From ed0f4f9f4429283000066c366b7fbc53ac6743da Mon Sep 17 00:00:00 2001 From: Danny Farrell <16297104+danpf@users.noreply.github.com> Date: Thu, 16 Nov 2023 11:57:07 -0600 Subject: [PATCH] Squash 2 --- .github/workflows/cpp.yml | 92 +++++ .github/workflows/emscripten.yml | 61 +++ .github/workflows/pip.yml | 46 +++ .github/workflows/wheels.yml | 89 +++++ .gitignore | 168 ++++++++ .gitmodules | 18 +- .travis.yml | 129 +++++-- CHANGELOG.md | 6 +- CMakeLists.txt | 32 +- Catch2 | 1 - README.md | 66 +++- bindings/4lgr.mmtf | Bin 41493 -> 0 bytes bindings/bindings.cpp | 189 --------- bindings/check.py | 7 - bindings/compile.sh | 2 - bindings/mmtf_t.py | 173 --------- ci/build_and_run_python_tests.sh | 10 + ci/build_and_run_tests.sh | 10 + ci/setup-travis.sh | 14 +- ci/travis-test-example.sh | 8 +- examples/CMakeLists.txt | 7 +- include/mmtf.hpp | 2 + include/mmtf/binary_decoder.hpp | 120 +++--- include/mmtf/binary_encoder.hpp | 28 +- include/mmtf/map_decoder.hpp | 16 +- include/mmtf/structure_data.hpp | 2 +- mmtf_spec | 1 - msgpack-c | 1 - pybind11 | 1 - pyproject.toml | 85 ++++ src/python/CMakeLists.txt | 21 + src/python/bindings.cpp | 515 +++++++++++++++++++++++++ src/python/mmtf_cppy/__init__.py | 31 ++ src/python/mmtf_cppy/structure_data.py | 495 ++++++++++++++++++++++++ src/python/tests/tests.py | 196 ++++++++++ submodules/mmtf_spec | 1 + tests/CMakeLists.txt | 18 +- tests/mmtf_tests.cpp | 82 ++-- tests/multi_cpp_test.cpp | 2 +- 39 files changed, 2161 insertions(+), 584 deletions(-) create mode 100644 .github/workflows/cpp.yml create mode 100644 .github/workflows/emscripten.yml create mode 100644 .github/workflows/pip.yml create mode 100644 .github/workflows/wheels.yml delete mode 160000 Catch2 delete mode 100644 bindings/4lgr.mmtf delete mode 100644 bindings/bindings.cpp delete mode 100644 bindings/check.py delete mode 100755 bindings/compile.sh delete mode 100644 bindings/mmtf_t.py create mode 100755 ci/build_and_run_python_tests.sh create mode 100755 ci/build_and_run_tests.sh delete mode 160000 mmtf_spec delete mode 160000 msgpack-c delete mode 160000 pybind11 create mode 100644 pyproject.toml create mode 100644 src/python/CMakeLists.txt create mode 100644 src/python/bindings.cpp create mode 100644 src/python/mmtf_cppy/__init__.py create mode 100644 src/python/mmtf_cppy/structure_data.py create mode 100644 src/python/tests/tests.py create mode 160000 submodules/mmtf_spec diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml new file mode 100644 index 0000000..6f74bca --- /dev/null +++ b/.github/workflows/cpp.yml @@ -0,0 +1,92 @@ +name: "cpp" + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build and test cpp + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + cc: gcc + cxx: g++ + - os: ubuntu-22.04 + cc: gcc + cxx: g++ + env_list: EMSCRIPTEN=ON + - os: ubuntu-22.04 + cc: clang + cxx: clang++ + - os: ubuntu-22.04 + cc: gcc + cxx: g++ + cmake_args: "-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS='-march=native'" + - os: macos-latest + cc: clang + cxx: clang++ + - os: windows-latest + cc: '' + cxx: '' + runs-on: ${{ matrix.os }} + env: + CC: ${{ matrix.cc }} + CXX: ${{ matrix.cxx }} + CMAKE_ARGS: ${{ matrix.cmake_args }} + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + + # - name: install mingw & deps + # uses: msys2/setup-msys2@v2 + # with: + # update: true + # install: >- + # git + # make + # pacboy: >- + # toolchain:p + # cmake:p + # ninja:p + # if: matrix.os == 'windows-latest' + + - name: Set environment list variables + run: | + env_vars="${{ matrix.env_list }}" + for var in $env_vars; do + echo "$var" >> $GITHUB_ENV + done + if: matrix.os != 'windows-latest' + + - name: Setup cmake + uses: jwlawson/actions-setup-cmake@v1.13 + with: + cmake-version: '3.16.x' + + - uses: seanmiddleditch/gha-setup-ninja@master + + - name: build and test + run: | + mkdir build && cd build + cmake $CMAKE_ARGS -G Ninja -Dmmtf_build_examples=ON -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=debug .. + ninja + ./tests/mmtf_tests + ./tests/multi_cpp_test + ./examples/mmtf_demo ../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf + ./examples/traverse ../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf + ./examples/traverse ../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf json + ./examples/traverse ../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf print + ./examples/print_as_pdb ../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml new file mode 100644 index 0000000..4c200d9 --- /dev/null +++ b/.github/workflows/emscripten.yml @@ -0,0 +1,61 @@ +name: WASM + +on: + workflow_dispatch: + pull_request: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-wasm-emscripten: + name: Pyodide + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + + - uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install pyodide-build + run: pip install pyodide-build==0.23.4 + + - name: Compute emsdk version + id: compute-emsdk-version + run: | + pyodide xbuildenv install --download + EMSCRIPTEN_VERSION=$(pyodide config get emscripten_version) + echo "emsdk-version=$EMSCRIPTEN_VERSION" >> $GITHUB_OUTPUT + + - uses: mymindstorm/setup-emsdk@v12 + with: + version: ${{ steps.compute-emsdk-version.outputs.emsdk-version }} + actions-cache-folder: emsdk-cache + + # A future version of pyodide may switch to -fwasm-exceptions + - name: Build + run: CFLAGS=-fexceptions LDFLAGS=-fexceptions pyodide build + + - uses: actions/upload-artifact@v3 + with: + path: dist/*.whl + + - uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Set up Pyodide virtual environment + run: | + pyodide venv .venv-pyodide + .venv-pyodide/bin/pip install $(echo -n dist/*.whl) + + - name: Test + run: .venv-pyodide/bin/python -m unittest src/python/tests/tests.py + diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml new file mode 100644 index 0000000..cfa5cbb --- /dev/null +++ b/.github/workflows/pip.yml @@ -0,0 +1,46 @@ +name: "Pip" + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build with Pip + runs-on: ${{ matrix.platform }} + strategy: + fail-fast: false + matrix: + platform: [windows-latest, macos-latest, ubuntu-latest] + python-version: ["3.8", "3.11", "3.12", "pypy-3.8"] + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + # - name: Setup cmake + # uses: jwlawson/actions-setup-cmake@v1.13 + # with: + # cmake-version: '3.16.x' + + # - uses: seanmiddleditch/gha-setup-ninja@master + + - name: Build and install + run: pip install --verbose . + + - name: Test + run: python src/python/tests/tests.py + diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 0000000..e4f92b5 --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,89 @@ +name: Wheels + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + release: + types: + - published + +env: + FORCE_COLOR: 3 + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build_sdist: + name: Build SDist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Build SDist + run: pipx run build --sdist + + - name: Check metadata + run: pipx run twine check dist/* + + - uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz + + + build_wheels: + name: Wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + # - name: Set CMAKE_GENERATOR for Windows + # if: matrix.os == 'windows-latest' + # run: echo "CMAKE_GENERATOR=MinGW Makefiles" >> $GITHUB_ENV + + - uses: pypa/cibuildwheel@v2.16.2 + env: + CIBW_ARCHS_MACOS: universal2 + CIBW_ARCHS_WINDOWS: auto ARM64 + CMAKE_GENERATOR: ${{ env.CMAKE_GENERATOR }} + + - name: Verify clean directory + run: git diff --exit-code + shell: bash + + - uses: actions/upload-artifact@v3 + with: + path: wheelhouse/*.whl + + upload_all: + name: Upload if release + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + + steps: + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.pypi_password }} diff --git a/.gitignore b/.gitignore index a8de19a..e649fb8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,171 @@ build/* docs/html/* examples/out/* examples/out_json_ref/* + +# python eggs +src/python/*.egg-info +**/__pycache__ +**/*.pyc + + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + diff --git a/.gitmodules b/.gitmodules index 3cd3c3c..1582527 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,6 @@ -[submodule "Catch2"] - path = Catch2 - url = https://github.com/catchorg/Catch2 -[submodule "msgpack-c"] - path = msgpack-c - url = https://github.com/msgpack/msgpack-c -[submodule "mmtf_spec"] - path = mmtf_spec - url = https://github.com/rcsb/mmtf -[submodule "pybind11"] - path = pybind11 - url = git@github.com:pybind/pybind11.git +[submodule "submodules/pybind11"] + path = submodules/pybind11 + url = https://github.com/pybind/pybind11.git +[submodule "submodules/mmtf_spec"] + path = submodules/mmtf_spec + url = https://github.com/rcsb/mmtf.git diff --git a/.travis.yml b/.travis.yml index 401c088..70fd8ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,57 +1,106 @@ +--- language: cpp sudo: false dist: trusty linux64_addons: addons: &linux64 - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.8 + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 linux32_addons: addons: &linux32 - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.8 - - g++-4.8-multilib - - linux-libc-dev:i386 - - libc6-dev-i386 + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 + - g++-4.8-multilib + - linux-libc-dev:i386 + - libc6-dev-i386 +linux64_cpp17addons: + addons: &linux64cpp17 + apt: + sources: + - ubuntu-toolchain-r-test + +linux64_cpp17addons: + addons: &linux64cpp17 + apt: + sources: + - ubuntu-toolchain-r-test + + +linux64_cpp17addons_py: + addons: &linux64cpp17py + apt: + sources: + - ubuntu-toolchain-r-test + - gcc-7 + - g++-7 + +python_test_command_sub: &python_test_command TEST_COMMAND=$TRAVIS_BUILD_DIR/ci/build_and_run_python_tests.sh CC=gcc # Set empty values for allow_failures to work -env: +env: TEST_COMMAND=$TRAVIS_BUILD_DIR/ci/build_and_run_tests.sh +python: matrix: - fast_finish: true - include: - - os: linux - env: EMSCRIPTEN=ON - addons: *linux64 - - os: linux - compiler: clang - addons: *linux64 - - os: linux - compiler: gcc - env: ARCH=x86 CMAKE_EXTRA=-DHAVE_LIBM=/lib32/libm.so.6 - addons: *linux32 - - os: osx - compiler: clang + fast_finish: true + include: + - os: linux + env: EMSCRIPTEN=ON TEST_COMMAND=$TRAVIS_BUILD_DIR/ci/build_and_run_tests.sh + addons: *linux64 + - os: linux + compiler: clang + addons: *linux64 + - os: linux + compiler: gcc + env: ARCH=x86 CMAKE_EXTRA=-DHAVE_LIBM=/lib32/libm.so.6 TEST_COMMAND=$TRAVIS_BUILD_DIR/ci/build_and_run_tests.sh + addons: *linux32 + - os: osx + compiler: clang + - os: linux + compiler: gcc + env: CMAKE_EXTRA="-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS='-march=native'" TEST_COMMAND=$TRAVIS_BUILD_DIR/ci/build_and_run_tests.sh + addons: *linux64cpp17 + dist: bionic + - os: linux + compiler: gcc + addons: *linux64cpp17 + dist: bionic + - os: linux + compiler: gcc + addons: *linux64cpp17py + dist: bionic + env: *python_test_command + python: 3.8 + language: python + - os: linux + compiler: gcc + addons: *linux64cpp17py + dist: bionic + env: *python_test_command + python: 3.11 + language: python + - os: linux + compiler: gcc + addons: *linux64cpp17py + dist: bionic + env: *python_test_command + python: 3.12 + language: python before_install: - # Setting environement - - cd $TRAVIS_BUILD_DIR - - source ci/setup-travis.sh - - $CC --version - - $CXX --version + # Setting environement + - cd $TRAVIS_BUILD_DIR + - source ci/setup-travis.sh + - $CC --version + - $CXX --version script: - - cd $TRAVIS_BUILD_DIR - - mkdir build && cd build - - $CMAKE_CONFIGURE cmake $CMAKE_ARGS $CMAKE_EXTRA .. - - make -j2 - - ctest -j2 --output-on-failure - - bash $TRAVIS_BUILD_DIR/ci/travis-test-example.sh - - cd $TRAVIS_BUILD_DIR + - echo $TEST_COMMAND + - (eval "$TEST_COMMAND") diff --git a/CHANGELOG.md b/CHANGELOG.md index 8962d68..f4da72e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] + +## v1.1.0 - 2022-10-03 ### Added - New mapDecoderFrom.. functions to decode only part of an MMTF file - Support for extra fields in MMTF files according to the @@ -15,10 +17,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/). [MMTF specification](https://github.com/rcsb/mmtf/pull/35). - New methods to find polymer chains and HETATM following discussions in [rcsb/mmtf#28](https://github.com/rcsb/mmtf/issues/28). +- Altered submodule locations [rcsb/mmtf-cpp#37](https://github.com/rcsb/mmtf-cpp/pull/37) + from the base directory to the new submodules directory. ## v1.0.0 - 2019-02-05 ### Added - Initial release including decoder and encoder for the [MMTF specification 1.0](https://github.com/rcsb/mmtf/blob/v1.0/spec.md). -[Unreleased]: https://github.com/rcsb/mmtf-cpp/compare/v1.0.0...HEAD +[Unreleased]: https://github.com/rcsb/mmtf-cpp/compare/v1.1.0...HEAD diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d95e07..9e4d72f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,10 @@ - -cmake_minimum_required(VERSION 3.5 FATAL_ERROR) +cmake_minimum_required(VERSION 3.15...3.26 FATAL_ERROR) project(mmtf-cpp VERSION 1.0.0 LANGUAGES CXX) -option(mmtf_build_local "Use the submodule dependencies for building" OFF) option(mmtf_build_examples "Build the examples" OFF) +SET(MSGPACK_USE_BOOST OFF CACHE BOOL "msgpack-c uses boost by default, lets keep that off") +# option(MSGPACK_USE_BOOST "msgpack-c uses boost by default, lets keep that off" OFF) add_library(MMTFcpp INTERFACE) target_compile_features(MMTFcpp INTERFACE cxx_auto_type) @@ -13,23 +13,23 @@ target_include_directories(MMTFcpp INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -if (mmtf_build_local) - # use header only - set(MSGPACKC_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/msgpack-c/include) - add_library(msgpackc INTERFACE) - target_include_directories(msgpackc INTERFACE ${MSGPACKC_INCLUDE_DIR}) - if (BUILD_TESTS) - set(CATCH_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Catch2/single_include) - add_library(Catch INTERFACE) - target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR}) - endif() +include(FetchContent) +FetchContent_Declare( + msgpack-cxx + GIT_REPOSITORY https://github.com/msgpack/msgpack-c.git + GIT_TAG cpp-6.1.0) +FetchContent_MakeAvailable(msgpack-cxx) + +if(WIN32) + target_link_libraries(MMTFcpp INTERFACE ws2_32) endif() -if (NOT TARGET msgpackc) - find_package(msgpack) +target_link_libraries(MMTFcpp INTERFACE msgpack-cxx) + +if (build_py) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/python) endif() -target_link_libraries(MMTFcpp INTERFACE msgpackc) if (BUILD_TESTS) enable_testing() diff --git a/Catch2 b/Catch2 deleted file mode 160000 index cf4b7ee..0000000 --- a/Catch2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cf4b7eead92773932f32c7efd2612e9d27b07557 diff --git a/README.md b/README.md index d6964a5..4fa1df8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ -[![Release](https://img.shields.io/github/release/rcsb/mmtf-cpp.svg?style=flat)](https://github.com/rcsb/mmtf-cpp/releases) -[![License](https://img.shields.io/github/license/rcsb/mmtf-cpp.svg?style=flat)](https://github.com/rcsb/mmtf-cpp/blob/master/LICENSE) -[![Build Status (Travis)](https://img.shields.io/travis/rcsb/mmtf-cpp/master.svg?style=flat)](https://travis-ci.org/rcsb/mmtf-cpp) -[![Build Status (AppVeyor)](https://img.shields.io/appveyor/ci/rcsb/mmtf-cpp/master.svg?style=flat)](https://ci.appveyor.com/project/rcsb/mmtf-cpp) +[![Release](https://img.shields.io/github/v/release/rcsb/mmtf-cpp)](https://github.com/rcsb/mmtf-cpp/releases) +[![License](https://img.shields.io/github/license/rcsb/mmtf-cpp)](https://github.com/rcsb/mmtf-cpp/blob/master/LICENSE) +[![Build Status (Travis)](https://img.shields.io/travis/com/rcsb/mmtf-cpp/master)](https://app.travis-ci.com/github/rcsb/mmtf-cpp) The macromolecular transmission format ([MMTF](http://mmtf.rcsb.org)) is a binary encoding of biological structures. @@ -46,6 +45,53 @@ Here, `` and `` are the paths to the For your more complicated projects, a `CMakeLists.txt` is included for you. + +### Python bindings + +The C++ MMTF library now can build python bindings using pybind11. To use them +you must have A) a c++11 compatible compiler and B) python >= 3.6 + +to install, it is as simple as `pip install .` + +(in the future possible `pip install mmtf-cpp`) + +```python +from mmtf_cppy import StructureData +import numpy as np +import math + + +def rotation_matrix(axis, theta): + """ + Return the rotation matrix associated with counterclockwise rotation about + the given axis by theta radians. + from https://stackoverflow.com/a/6802723 + """ + axis = np.asarray(axis) + axis = axis / math.sqrt(np.dot(axis, axis)) + a = math.cos(theta / 2.0) + b, c, d = -axis * math.sin(theta / 2.0) + aa, bb, cc, dd = a * a, b * b, c * c, d * d + bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d + return np.array([[aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)], + [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)], + [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc]]) + + +theta = 1.2 +axis = [0, 0, 1] + +sd = StructureData("my_favorite_structure.mmtf") +sd.atomProperties["pymol_colorList"] = [1 if x % 2 == 0 else 5 for x in sd.xCoordList] +xyz = np.column_stack((sd.xCoordList, sd.yCoordList, sd.zCoordList)) +xyz_rot = rotation_matrix(axis, theta).dot(xyz.T).T +sd.xCoordList, sd.yCoordList, sd.zCoordList = np.hsplit(xyz_rot, 3) +sd.write_to_file("my_favorite_structure_rot.mmtf") + +``` + + + ## Installation You can also perform a system wide installation with `cmake` and `ninja` (or `make`). To do so: @@ -73,7 +119,7 @@ To build the tests + examples we recommend using the following lines: git submodule update --init --recursive mkdir build cd build -cmake -G Ninja -DBUILD_TESTS=ON -Dmmtf_build_local=ON -Dmmtf_build_examples=ON .. +cmake -G Ninja -DBUILD_TESTS=ON -Dmmtf_build_examples=ON .. ninja chmod +x ./tests/mmtf_tests ./tests/mmtf_tests @@ -83,18 +129,18 @@ Example codes: - mmtf_demo.cpp: Loads an MMTF file and checks internal consistency using mmtf::StructureData::hasConsistentData. ```bash -./examples/mmtf_demo ../mmtf_spec/test-suite/mmtf/173D.mmtf +./examples/mmtf_demo ../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf ``` - traverse.cpp: Loads an MMTF file and dumps it in human-readable forms. ```bash -./examples/traverse ../mmtf_spec/test-suite/mmtf/173D.mmtf -./examples/traverse ../mmtf_spec/test-suite/mmtf/173D.mmtf json -./examples/traverse ../mmtf_spec/test-suite/mmtf/173D.mmtf print +./examples/traverse ../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf +./examples/traverse ../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf json +./examples/traverse ../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf print ``` - print_as_pdb.cpp: Loads an MMTF file and prints it in pdb format. ```bash -./examples/print_as_pdb ../mmtf_spec/test-suite/mmtf/173D.mmtf +./examples/print_as_pdb ../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf ``` ## Benchmark diff --git a/bindings/4lgr.mmtf b/bindings/4lgr.mmtf deleted file mode 100644 index 53d34fb74f38b0e962e40a8a66fdd9c8da94767c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41493 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 diff --git a/bindings/bindings.cpp b/bindings/bindings.cpp deleted file mode 100644 index 7059ce4..0000000 --- a/bindings/bindings.cpp +++ /dev/null @@ -1,189 +0,0 @@ - -//#ifndef MMTF_CPP_PYBIND_BINDINGS_HH -//#define MMTF_CPP_PYBIND_BINDINGS_HH - -#include - -#include -#include -#include -#include -#include -#include - -namespace py = pybind11; - -std::string -array2str1(py::array const & arr) { - std::vector array_vec(arr.size()); - std::memcpy(array_vec.data(),arr.data(),arr.size()*sizeof(double)); - std::string ret("w"); - return ret; -} - -std::string -array2str2(std::vector const & arr) { - //if (arr.empty()) return "[]"; - //std::stringstream ss; - //std::string const delim(", "); - //for (float const & f : arr) { - // ss << f << delim; - //} - //std::string ret(ss.str()); - //ret.pop_back(); ret.pop_back(); - std::string ret("x"); - return ret; -} - -struct Pet { - Pet(const std::string &name) : name(name) { - for (int i=0; i< 1000000; ++i) { - bdata.push_back(i); - } - } - void setName(const std::string &name_) { name = name_; } - const std::string &getName() const { return name; } - - std::string name; - std::vector bdata; -}; - -template< typename T > -py::array -array1d_from_vector(std::vector & m) { - if (m.empty()) return py::array_t(); - std::vector* ptr = new std::vector(std::move(m)); - auto capsule = py::capsule(ptr, [](void* p) { delete reinterpret_cast*>(p); }); - return py::array_t(ptr->size(), // shape of array - ptr->data(), // c-style contiguous strides for Sequence - capsule // numpy array references this parent - ); -} - - -template< typename T > -py::array -array2d_from_vector(std::vector> & m) { - if (m.empty()) return py::array_t(); - std::vector>* ptr = new std::vector>(std::move(m)); - auto capsule = py::capsule(ptr, [](void* p) { delete reinterpret_cast>*>(p); }); - return py::array_t({ptr->size(), ptr->at(0).size()}, // shape of array - {ptr->size()*ptr->at(0).size()*sizeof(T)}, // c-style contiguous strides for Sequence - capsule // numpy array references this parent - ); -} - -py::bytes -raw_properties(mmtf::StructureData const & sd) { - std::stringstream bytes; - std::map< std::string, std::map< std::string, msgpack::object > > objs({ - {"bondProperties", sd.bondProperties }, - {"atomProperties", sd.atomProperties }, - {"groupProperties", sd.groupProperties }, - {"chainProperties", sd.chainProperties }, - {"modelProperties", sd.modelProperties }, - {"extraProperties", sd.extraProperties }}); - msgpack::pack(bytes, objs); - return py::bytes(bytes.str().data()); -} - - - -PYBIND11_MODULE(example, m) { - py::class_(m, "Pet") - .def(py::init()) - .def("setName", &Pet::setName) - .def("getName", &Pet::getName) - .def_readwrite("bdata", &Pet::bdata) - .def("bdata2", [](Pet &m) -> py::array { - py::buffer_info buff_info(py::buffer_info( - m.bdata.data(), /* Pointer to buffer */ - sizeof(float), /* Size of one scalar */ - py::format_descriptor::format(), /* Python struct-style format descriptor */ - m.bdata.size() /* Number of dimensions */ - )); - return py::array(buff_info); - }); - m.def("array2str1", &array2str1, "array impl"); - m.def("array2str2", &array2str2, "vector impl"); -//} -// -//PYBIND11_MODULE(notsure, m) { - // new stuff here - py::class_(m, "CPPStructureData") - .def( pybind11::init( [](){ return new mmtf::StructureData(); } ) ) - .def( pybind11::init( [](mmtf::StructureData const &o){ return new mmtf::StructureData(o); } ) ) - .def_readwrite("mmtfVersion", &mmtf::StructureData::mmtfVersion) - .def_readwrite("mmtfProducer", &mmtf::StructureData::mmtfProducer) - .def("unitCell", [](mmtf::StructureData &m){return array1d_from_vector(m.unitCell);}) - .def_readwrite("spaceGroup", &mmtf::StructureData::spaceGroup) - .def_readwrite("structureId", &mmtf::StructureData::structureId) - .def_readwrite("title", &mmtf::StructureData::title) - .def_readwrite("depositionDate", &mmtf::StructureData::depositionDate) - .def_readwrite("releaseDate", &mmtf::StructureData::releaseDate) - //.def("ncsOperatorList", [](mmtf::StructureData &m){return array2d_from_vector(m.ncsOperatorList, 16);}) - .def("ncsOperatorList", [](mmtf::StructureData &m){return array2d_from_vector(m.ncsOperatorList);}) - .def_readwrite("bioAssemblyList", &mmtf::StructureData::bioAssemblyList) - .def_readwrite("entityList", &mmtf::StructureData::entityList) - .def_readwrite("experimentalMethods", &mmtf::StructureData::experimentalMethods) - .def_readwrite("resolution", &mmtf::StructureData::resolution) - .def_readwrite("rFree", &mmtf::StructureData::rFree) - .def_readwrite("rWork", &mmtf::StructureData::rWork) - .def_readwrite("numBonds", &mmtf::StructureData::numBonds) - .def_readwrite("numAtoms", &mmtf::StructureData::numAtoms) - .def_readwrite("numGroups", &mmtf::StructureData::numGroups) - .def_readwrite("numChains", &mmtf::StructureData::numChains) - .def_readwrite("numModels", &mmtf::StructureData::numModels) - .def_readwrite("groupList", &mmtf::StructureData::groupList) - .def("unitCell", [](mmtf::StructureData &m){return array1d_from_vector(m.unitCell);}) - .def("bondAtomList", [](mmtf::StructureData &m){return array1d_from_vector(m.bondAtomList);}) - .def("bondOrderList", [](mmtf::StructureData &m){return array1d_from_vector(m.bondOrderList);}) - .def("bondResonanceList", [](mmtf::StructureData &m){return array1d_from_vector(m.bondResonanceList);}) - .def("xCoordList", [](mmtf::StructureData &m){return array1d_from_vector(m.xCoordList);}) - .def("yCoordList", [](mmtf::StructureData &m){return array1d_from_vector(m.yCoordList);}) - .def("zCoordList", [](mmtf::StructureData &m){return array1d_from_vector(m.zCoordList);}) - .def("bFactorList", [](mmtf::StructureData &m){return array1d_from_vector(m.bFactorList);}) - .def("atomIdList", [](mmtf::StructureData &m){return array1d_from_vector(m.atomIdList);}) - .def_readwrite("altLocList", &mmtf::StructureData::altLocList) - .def("occupancyList", [](mmtf::StructureData &m){return array1d_from_vector(m.occupancyList);}) - .def("groupIdList", [](mmtf::StructureData &m){return array1d_from_vector(m.groupIdList);}) - .def("groupTypeList", [](mmtf::StructureData &m){return array1d_from_vector(m.groupTypeList);}) - .def("secStructList", [](mmtf::StructureData &m){return array1d_from_vector(m.secStructList);}) - .def_readwrite("insCodeList", &mmtf::StructureData::insCodeList) - .def("sequenceIndexList", [](mmtf::StructureData &m){return array1d_from_vector(m.sequenceIndexList);}) - .def_readwrite("chainIdList", &mmtf::StructureData::chainIdList) - .def_readwrite("chainNameList", &mmtf::StructureData::chainNameList) - .def("groupsPerChain", [](mmtf::StructureData &m){return array1d_from_vector(m.groupsPerChain);}) - .def("chainsPerModel", [](mmtf::StructureData &m){return array1d_from_vector(m.chainsPerModel);}) - .def("raw_properties", [](mmtf::StructureData const &m){return raw_properties(m);}); - //cl.def_readonly("msgpack_zone", &mmtf::StructureData::msgpack_zone); - //cl.def_readwrite("bondProperties", &mmtf::StructureData::bondProperties); - //cl.def_readwrite("atomProperties", &mmtf::StructureData::atomProperties); - //cl.def_readwrite("groupProperties", &mmtf::StructureData::groupProperties); - //cl.def_readwrite("chainProperties", &mmtf::StructureData::chainProperties); - //cl.def_readwrite("modelProperties", &mmtf::StructureData::modelProperties); - //cl.def_readwrite("extraProperties", &mmtf::StructureData::extraProperties); - m.def("decodeFromFile", &mmtf::decodeFromFile, "decode a mmtf::StructureData from a file"); - - py::class_(m, "CPPBioAssembly") - .def( pybind11::init( [](){ return new mmtf::BioAssembly(); } ) ) - .def( pybind11::init( [](mmtf::BioAssembly const &o){ return new mmtf::BioAssembly(o); } ) ) - .def_readwrite("name", &mmtf::BioAssembly::name); - // TODO ^^ insert transformlist - py::class_(m, "CPPTransform") - .def( pybind11::init( [](){ return new mmtf::Transform(); } ) ) - .def( pybind11::init( [](mmtf::Transform const &o){ return new mmtf::Transform(o); } ) ); - /// TODO finish ^^ - py::class_(m, "CPPGroupType") - .def( pybind11::init( [](){ return new mmtf::GroupType(); } ) ) - .def( pybind11::init( [](mmtf::GroupType const &o){ return new mmtf::GroupType(o); } ) ); - /// TODO finish ^^ - py::class_(m, "CPPEntity") - .def( pybind11::init( [](){ return new mmtf::Entity(); } ) ) - .def( pybind11::init( [](mmtf::Entity const &o){ return new mmtf::Entity(o); } ) ); - - -} - - -//#endif diff --git a/bindings/check.py b/bindings/check.py deleted file mode 100644 index 454d46d..0000000 --- a/bindings/check.py +++ /dev/null @@ -1,7 +0,0 @@ -import numpy as np -import example -import time - -p = example.Pet("xx") - -m diff --git a/bindings/compile.sh b/bindings/compile.sh deleted file mode 100755 index 7dfe9b7..0000000 --- a/bindings/compile.sh +++ /dev/null @@ -1,2 +0,0 @@ -c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` -I../pybind11/include -I../msgpack-c/include -I../include bindings.cpp -o example`python3-config --extension-suffix` -python mmtf_t.py diff --git a/bindings/mmtf_t.py b/bindings/mmtf_t.py deleted file mode 100644 index 8cdc1d3..0000000 --- a/bindings/mmtf_t.py +++ /dev/null @@ -1,173 +0,0 @@ - -import time -import numpy as np -import mmtf -import msgpack - -import example -from example import CPPStructureData as CPPSD, decodeFromFile - -class StructureData: - def __init__(self, file_name=None, file_bytes=None): - if file_name: - self.init_from_file_name(file_name) - elif file_bytes: - self.init_from_raw_bytes(file_bytes) - else: - self.raw_init() - - def init_from_file_name(self, file_name: str): - cppsd = CPPSD() - decodeFromFile(cppsd, file_name) - self.init_from_cppsd(cppsd) - - def init_from_cppsd(self, cppsd: 'CPPStructureData'): - self.mmtfVersion = cppsd.mmtfVersion - self.mmtfProducer = cppsd.mmtfProducer - self.unitCell = cppsd.unitCell() - self.spaceGroup = cppsd.spaceGroup - self.structureId = cppsd.structureId - self.title = cppsd.title - self.depositionDate = cppsd.depositionDate - self.releaseDate = cppsd.releaseDate - self.ncsOperatorList = cppsd.ncsOperatorList() - # self.bioAssemblyList = cppsd.bioAssemblyList - # self.entityList = cppsd.entityList - self.experimentalMethods = cppsd.experimentalMethods - self.resolution = cppsd.resolution - self.rFree = cppsd.rFree - self.rWork = cppsd.rWork - self.numBonds = cppsd.numBonds - self.numAtoms = cppsd.numAtoms - self.numGroups = cppsd.numGroups - self.numChains = cppsd.numChains - self.numModels = cppsd.numModels - # self.groupList = cppsd.groupList - - self.bondAtomList = cppsd.bondAtomList() - self.bondOrderList = cppsd.bondOrderList() - self.bondResonanceList = cppsd.bondResonanceList() - self.xCoordList = cppsd.xCoordList() - self.yCoordList = cppsd.yCoordList() - self.zCoordList = cppsd.zCoordList() - self.bFactorList = cppsd.bFactorList() - self.atomIdList = cppsd.atomIdList() - self.altLocList = cppsd.altLocList - self.occupancyList = cppsd.occupancyList() - # print(type(cppsd.groupIdList())) - self.groupIdList = cppsd.groupIdList() - # print(self.groupIdList) - self.groupIdList = cppsd.groupIdList() - self.groupTypeList = cppsd.groupTypeList() - self.secStructList = cppsd.secStructList() - self.insCodeList = cppsd.insCodeList - self.sequenceIndexList = cppsd.sequenceIndexList() - self.chainIdList = cppsd.chainIdList - self.chainNameList = cppsd.chainNameList - self.groupsPerChain = cppsd.groupsPerChain() - self.chainsPerModel = cppsd.chainsPerModel() - - - # self.bondAtomList = np.array(cppsd.bondAtomList(), copy=False) - # self.bondOrderList = np.array(cppsd.bondOrderList(), copy=False) - # self.bondResonanceList = np.array(cppsd.bondResonanceList(), copy=False) - # self.xCoordList = np.array(cppsd.xCoordList(), copy=False) - # self.yCoordList = np.array(cppsd.yCoordList(), copy=False) - # self.zCoordList = np.array(cppsd.zCoordList(), copy=False) - # self.bFactorList = np.array(cppsd.bFactorList(), copy=False) - # self.atomIdList = np.array(cppsd.atomIdList(), copy=False) - # self.altLocList = np.array(cppsd.altLocList, copy=False) - # self.occupancyList = np.array(cppsd.occupancyList(), copy=False) - # self.groupIdList = np.array(cppsd.groupIdList(), copy=False) - # # print(self.groupIdList) - # self.groupIdList = np.array(cppsd.groupIdList(), copy=False) - # self.groupTypeList = np.array(cppsd.groupTypeList(), copy=False) - # self.secStructList = np.array(cppsd.secStructList(), copy=False) - # self.insCodeList = np.array(cppsd.insCodeList, copy=False) - # self.sequenceIndexList = np.array(cppsd.sequenceIndexList(), copy=False) - # self.chainIdList = np.array(cppsd.chainIdList, copy=False) - # self.chainNameList = np.array(cppsd.chainNameList, copy=False) - # self.groupsPerChain = np.array(cppsd.groupsPerChain(), copy=False) - # self.chainsPerModel = np.array(cppsd.chainsPerModel(), copy=False) - - raw_properties = cppsd.raw_properties() - raw_properties = msgpack.unpackb(raw_properties, raw=False) - # print(type(raw_properties), len(raw_properties)) - self.bondProperties = raw_properties["bondProperties"] - self.atomProperties = raw_properties["atomProperties"] - self.groupProperties = raw_properties["groupProperties"] - self.chainProperties = raw_properties["chainProperties"] - self.modelProperties = raw_properties["modelProperties"] - self.extraProperties = raw_properties["extraProperties"] - - def raw_init(self): - self.mmtfVersion = None - self.mmtfProducer = None - self.unitCell = None - self.spaceGroup = None - self.structureId = None - self.title = None - self.depositionDate = None - self.releaseDate = None - self.ncsOperatorList = None - self.bioAssemblyList = None - self.entityList = None - self.experimentalMethods = None - self.resolution = None - self.rFree = None - self.rWork = None - self.numBonds = None - self.numAtoms = None - self.numGroups = None - self.numChains = None - self.numModels = None - self.groupList = None - self.bondAtomList = None - self.bondOrderList = None - self.bondResonanceList = None - self.xCoordList = None - self.yCoordList = None - self.zCoordList = None - self.bFactorList = None - self.atomIdList = None - self.altLocList = None - self.occupancyList = None - self.groupIdList = None - self.groupTypeList = None - self.secStructList = None - self.insCodeList = None - self.sequenceIndexList = None - self.chainIdList = None - self.chainNameList = None - self.groupsPerChain = None - self.chainsPerModel = None - self.bondProperties = None - self.atomProperties = None - self.groupProperties = None - self.chainProperties = None - self.modelProperties = None - self.extraProperties = None - - - -start = time.time() -for x in range(10000): - sd = StructureData("4lgr.mmtf") -stop = time.time() -python_t = stop-start -print("python", python_t) - -start = time.time() -for x in range(1000): - sd = mmtf.parse("4lgr.mmtf") -stop = time.time() -python_t = stop-start -print("python og", python_t) - -start = time.time() -for x in range(10000): - cppsd = CPPSD() - decodeFromFile(cppsd, "4lgr.mmtf") -stop = time.time() -cpp_t = stop-start -print("cpp", cpp_t) diff --git a/ci/build_and_run_python_tests.sh b/ci/build_and_run_python_tests.sh new file mode 100755 index 0000000..e2704c9 --- /dev/null +++ b/ci/build_and_run_python_tests.sh @@ -0,0 +1,10 @@ + +python3 --version +pip3 --version +pip3 install -r requirements.txt +pip3 install -r requirements-dev.txt +cd $TRAVIS_BUILD_DIR +pip3 install . +pytest python_src/tests/tests.py -s -vv + + diff --git a/ci/build_and_run_tests.sh b/ci/build_and_run_tests.sh new file mode 100755 index 0000000..c56538b --- /dev/null +++ b/ci/build_and_run_tests.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +cd "$TRAVIS_BUILD_DIR" +mkdir build && cd build +$CMAKE_CONFIGURE cmake $CMAKE_ARGS $CMAKE_EXTRA .. +make -j2 +ctest -j2 --output-on-failure +bash $TRAVIS_BUILD_DIR/ci/travis-test-example.sh +cd $TRAVIS_BUILD_DIR diff --git a/ci/setup-travis.sh b/ci/setup-travis.sh index 4554348..0fb5b25 100644 --- a/ci/setup-travis.sh +++ b/ci/setup-travis.sh @@ -25,9 +25,17 @@ if [[ "$EMSCRIPTEN" == "ON" ]]; then fi if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - if [[ "$CC" == "gcc" ]]; then - export CC=gcc-4.8 - export CXX=g++-4.8 + if [[ "$TRAVIS_DIST" == "trusty" ]]; then + if [[ "$CC" == "gcc" ]]; then + export CC=gcc-4.8 + export CXX=g++-4.8 + fi + fi + if [[ "$TRAVIS_DIST" == "bionic" ]]; then + if [[ "$CC" == "gcc" ]]; then + export CC=gcc-7 + export CXX=g++-7 + fi fi fi diff --git a/ci/travis-test-example.sh b/ci/travis-test-example.sh index 220e025..6772ac3 100644 --- a/ci/travis-test-example.sh +++ b/ci/travis-test-example.sh @@ -10,13 +10,13 @@ set -e cd $TRAVIS_BUILD_DIR/examples if [ -z "$EMSCRIPTEN" ]; then # Compile with C++03 forced - $CXX -I"../msgpack-c/include" -I"../include" -std=c++03 -O2 \ + $CXX -I"../submodules/msgpack-c/include" -I"../include" -std=c++03 -O2 \ -o read_and_write read_and_write.cpp - ./read_and_write ../mmtf_spec/test-suite/mmtf/3NJW.mmtf test.mmtf + ./read_and_write ../submodules/mmtf_spec/test-suite/mmtf/3NJW.mmtf test.mmtf else # Cannot do C++03 here and need to embed input file for running it with node - cp ../mmtf_spec/test-suite/mmtf/3NJW.mmtf . - $CXX -I"../msgpack-c/include" -I"../include" -O2 \ + cp ../submodules/mmtf_spec/test-suite/mmtf/3NJW.mmtf . + $CXX -I"../submodules/msgpack-c/include" -I"../include" -O2 \ -o read_and_write.js read_and_write.cpp --embed-file 3NJW.mmtf node read_and_write.js 3NJW.mmtf test.mmtf fi diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 10eb2b8..73e883e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,3 @@ - cmake_minimum_required(VERSION 3.5 FATAL_ERROR) SET(executables mmtf_demo traverse print_as_pdb tableexport read_and_write) @@ -6,10 +5,6 @@ SET(executables mmtf_demo traverse print_as_pdb tableexport read_and_write) foreach(exe ${executables}) add_executable(${exe} ${exe}.cpp) target_compile_features(${exe} PRIVATE cxx_auto_type) - if(WIN32) - target_link_libraries(${exe} MMTFcpp ws2_32) - else() - target_link_libraries(${exe} MMTFcpp) - endif() + target_link_libraries(${exe} MMTFcpp) endforeach(exe) diff --git a/include/mmtf.hpp b/include/mmtf.hpp index 34ec332..3d7224e 100644 --- a/include/mmtf.hpp +++ b/include/mmtf.hpp @@ -12,5 +12,7 @@ // single include for all MMTF needs +#define VERSION_INFO + #include "mmtf/decoder.hpp" #include "mmtf/encoder.hpp" diff --git a/include/mmtf/binary_decoder.hpp b/include/mmtf/binary_decoder.hpp index 00b39bb..2a4eadf 100644 --- a/include/mmtf/binary_decoder.hpp +++ b/include/mmtf/binary_decoder.hpp @@ -34,11 +34,25 @@ class BinaryDecoder { * Reads out binary header to prepare for call of decode. * @param[in] obj Object to decode. * @param[in] key Key used to report errors. + * @warning This uses a pointer to the data of obj. obj must stay alive + * while this instance exists or it will result in undefined behavior. * @throw mmtf::DecodeError if obj is not a binary or is too short. */ BinaryDecoder(const msgpack::object& obj, const std::string& key = "UNNAMED_BINARY"); + /** + * @brief Initialize object given a msgpack binary string. + * Reads out binary header to prepare for call of decode. + * @param[in] str Object to decode. + * @param[in] key Key used to report errors. + * @warning This uses a pointer to the data of str. str must stay alive + * while this instance exists or it will result in undefined behavior. + * @throw mmtf::DecodeError if obj is not a binary or is too short. + */ + BinaryDecoder(const std::string& str, + const std::string& key = "UNNAMED_BINARY"); + /** * @brief Decode binary msgpack object into the given target. * @@ -55,7 +69,7 @@ class BinaryDecoder { * @throw mmtf::DecodeError if we fail to decode. */ template - void decode(T& target); + void decode(T& target) const; private: // for error reporting @@ -67,42 +81,47 @@ class BinaryDecoder { const char* encodedData_; uint32_t encodedDataLength_; // max. size for binary is 2^32 - 1 + // helper function for constructors + void + initFromData(const char * str_data, + const std::size_t len); + // check length consistency (throws) - void checkLength_(int32_t exp_length); + void checkLength_(int32_t exp_length) const; // check if binary data is divisible by x (throws) - void checkDivisibleBy_(int32_t item_size); + void checkDivisibleBy_(int32_t item_size) const; // byte decoders - void decodeFromBytes_(std::vector& output); - void decodeFromBytes_(std::vector& output); - void decodeFromBytes_(std::vector& output); - void decodeFromBytes_(std::vector& output); + void decodeFromBytes_(std::vector& output) const; + void decodeFromBytes_(std::vector& output) const; + void decodeFromBytes_(std::vector& output) const; + void decodeFromBytes_(std::vector& output) const; // special one: decode to vector of strings - void decodeFromBytes_(std::vector& output); + void decodeFromBytes_(std::vector& output) const; // run length decoding // -> Int and IntOut can be any integer types // -> Int values are blindly converted to IntOut template void runLengthDecode_(const std::vector& input, - std::vector& output); + std::vector& output) const; // delta decoding -> Int can be any integer type template - void deltaDecode_(const std::vector& input, std::vector& output); + void deltaDecode_(const std::vector& input, std::vector& output) const; // variant doing it in-place template - void deltaDecode_(std::vector& in_out); + void deltaDecode_(std::vector& in_out) const; // recursive indexing decode -> SmallInt must be smaller than Int template void recursiveIndexDecode_(const std::vector& input, - std::vector& output); + std::vector& output) const; // decode integer to float -> Int can be any integer type template - void decodeDivide_(const std::vector& input, float divisor, - std::vector& output); + void decodeDivide_(const std::vector& input, float const divisor, + std::vector& output) const; }; // ************************************************************************* @@ -121,11 +140,17 @@ namespace { #ifndef __EMSCRIPTEN__ void assignBigendian4(void* dst, const char* src) { - *((uint32_t*)dst) = ntohl(*((uint32_t*)src)); + uint32_t tmp; + std::memcpy(&tmp, src, sizeof(uint32_t)); + tmp = ntohl(tmp); + std::memcpy(dst, &tmp, sizeof(uint32_t)); } void assignBigendian2(void* dst, const char* src) { - *((uint16_t*)dst) = ntohs(*((uint16_t*)src)); + uint16_t tmp; + std::memcpy(&tmp, src, sizeof(uint16_t)); + tmp = ntohs(tmp); + std::memcpy(dst, &tmp, sizeof(uint16_t)); } #else // Need to avoid how emscripten handles memory @@ -160,6 +185,16 @@ void arrayCopyBigendian2(void* dst, const char* src, size_t n) { } // anon ns + +// note this does not set key_, you must set it in ctor +inline void BinaryDecoder::initFromData(const char * bytes, std::size_t const len) { + assignBigendian4(&strategy_, bytes); + assignBigendian4(&length_, bytes + 4); + assignBigendian4(¶meter_, bytes + 8); + encodedData_ = bytes + 12; + encodedDataLength_ = len - 12; +} + inline BinaryDecoder::BinaryDecoder(const msgpack::object& obj, const std::string& key) : key_(key) { @@ -172,23 +207,22 @@ inline BinaryDecoder::BinaryDecoder(const msgpack::object& obj, err << "The '" + key + "' entry is too short " << obj.via.bin.size; throw DecodeError(err.str()); } - // get data (encoded data is only pointed to and not parsed here) - const char* bytes = obj.via.bin.ptr; + this->initFromData(obj.via.bin.ptr, obj.via.bin.size); +} - assignBigendian4(&strategy_, bytes); - assignBigendian4(&length_, bytes + 4); - assignBigendian4(¶meter_, bytes + 8); - encodedData_ = bytes + 12; - encodedDataLength_ = obj.via.bin.size - 12; +inline BinaryDecoder::BinaryDecoder(const std::string& str, + const std::string& key) + : key_(key) { + this->initFromData(str.data(), str.size()); } template -void BinaryDecoder::decode(T& target) { +void BinaryDecoder::decode(T&) const { throw mmtf::DecodeError("Invalid target type for binary '" + key_ + "'"); } template<> -inline void BinaryDecoder::decode(std::vector& output) { +inline void BinaryDecoder::decode(std::vector& output) const { // check strategy to parse switch (strategy_) { @@ -248,7 +282,7 @@ inline void BinaryDecoder::decode(std::vector& output) { } template<> -inline void BinaryDecoder::decode(std::vector& output) { +inline void BinaryDecoder::decode(std::vector& output) const { // check strategy to parse switch (strategy_) { @@ -275,7 +309,7 @@ inline void BinaryDecoder::decode(std::vector& output) { } template<> -inline void BinaryDecoder::decode(std::vector& output) { +inline void BinaryDecoder::decode(std::vector& output) const { // check strategy to parse switch (strategy_) { @@ -296,7 +330,7 @@ inline void BinaryDecoder::decode(std::vector& output) { } template<> -inline void BinaryDecoder::decode(std::vector& output) { +inline void BinaryDecoder::decode(std::vector& output) const { // check strategy to parse switch (strategy_) { @@ -342,7 +376,7 @@ inline void BinaryDecoder::decode(std::vector& output) { } template<> -inline void BinaryDecoder::decode(std::vector& output) { +inline void BinaryDecoder::decode(std::vector& output) const { // check strategy to parse switch (strategy_) { @@ -363,7 +397,7 @@ inline void BinaryDecoder::decode(std::vector& output) { } template<> -inline void BinaryDecoder::decode(std::vector& output) { +inline void BinaryDecoder::decode(std::vector& output) const { // check strategy to parse switch (strategy_) { @@ -386,7 +420,7 @@ inline void BinaryDecoder::decode(std::vector& output) { } // checks -inline void BinaryDecoder::checkLength_(int32_t exp_length) { +inline void BinaryDecoder::checkLength_(int32_t exp_length) const { if (length_ != exp_length) { std::stringstream err; err << "Length mismatch for binary '" + key_ + "': " @@ -395,7 +429,7 @@ inline void BinaryDecoder::checkLength_(int32_t exp_length) { } } -inline void BinaryDecoder::checkDivisibleBy_(int32_t item_size) { +inline void BinaryDecoder::checkDivisibleBy_(int32_t item_size) const { if (encodedDataLength_ % item_size != 0) { std::stringstream err; err << "Binary length of '" + key_ + "': " @@ -405,7 +439,7 @@ inline void BinaryDecoder::checkDivisibleBy_(int32_t item_size) { } // byte decoders -inline void BinaryDecoder::decodeFromBytes_(std::vector& output) { +inline void BinaryDecoder::decodeFromBytes_(std::vector& output) const { checkDivisibleBy_(4); // prepare memory output.resize(encodedDataLength_ / 4); @@ -414,7 +448,7 @@ inline void BinaryDecoder::decodeFromBytes_(std::vector& output) { arrayCopyBigendian4(&output[0], encodedData_, encodedDataLength_); } } -inline void BinaryDecoder::decodeFromBytes_(std::vector& output) { +inline void BinaryDecoder::decodeFromBytes_(std::vector& output) const { // prepare memory output.resize(encodedDataLength_); // get data @@ -422,7 +456,7 @@ inline void BinaryDecoder::decodeFromBytes_(std::vector& output) { memcpy(&output[0], encodedData_, encodedDataLength_); } } -inline void BinaryDecoder::decodeFromBytes_(std::vector& output) { +inline void BinaryDecoder::decodeFromBytes_(std::vector& output) const { checkDivisibleBy_(2); // prepare memory output.resize(encodedDataLength_ / 2); @@ -431,7 +465,7 @@ inline void BinaryDecoder::decodeFromBytes_(std::vector& output) { arrayCopyBigendian2(&output[0], encodedData_, encodedDataLength_); } } -inline void BinaryDecoder::decodeFromBytes_(std::vector& output) { +inline void BinaryDecoder::decodeFromBytes_(std::vector& output) const { checkDivisibleBy_(4); // prepare memory output.resize(encodedDataLength_ / 4); @@ -441,7 +475,7 @@ inline void BinaryDecoder::decodeFromBytes_(std::vector& output) { } } // special one: decode to vector of strings -inline void BinaryDecoder::decodeFromBytes_(std::vector& output) { +inline void BinaryDecoder::decodeFromBytes_(std::vector& output) const { char NULL_BYTE = 0x00; // check parameter const int32_t str_len = parameter_; @@ -458,7 +492,7 @@ inline void BinaryDecoder::decodeFromBytes_(std::vector& output) { // run length decoding template void BinaryDecoder::runLengthDecode_(const std::vector& input, - std::vector& output) { + std::vector& output) const { // we work with pairs of numbers checkDivisibleBy_(2); // find out size of resulting vector (for speed) @@ -482,7 +516,7 @@ void BinaryDecoder::runLengthDecode_(const std::vector& input, // delta decoding template void BinaryDecoder::deltaDecode_(const std::vector& input, - std::vector& output) { + std::vector& output) const { // reserve space (for speed) output.clear(); if (input.empty()) return; // ensure we have some values @@ -494,7 +528,7 @@ void BinaryDecoder::deltaDecode_(const std::vector& input, } } template -void BinaryDecoder::deltaDecode_(std::vector& in_out) { +void BinaryDecoder::deltaDecode_(std::vector& in_out) const { for (size_t i = 1; i < in_out.size(); ++i) { in_out[i] = in_out[i - 1] + in_out[i]; } @@ -503,7 +537,7 @@ void BinaryDecoder::deltaDecode_(std::vector& in_out) { // recursive indexing decode template void BinaryDecoder::recursiveIndexDecode_(const std::vector& input, - std::vector& output) { + std::vector& output) const { // get limits const SmallInt min_int = std::numeric_limits::min(); const SmallInt max_int = std::numeric_limits::max(); @@ -528,8 +562,8 @@ void BinaryDecoder::recursiveIndexDecode_(const std::vector& input, // decode integer to float template -void BinaryDecoder::decodeDivide_(const std::vector& input, float divisor, - std::vector& output) { +void BinaryDecoder::decodeDivide_(const std::vector& input, float const divisor, + std::vector& output) const { // reserve space and get inverted divisor (for speed) output.clear(); output.reserve(input.size()); diff --git a/include/mmtf/binary_encoder.hpp b/include/mmtf/binary_encoder.hpp index 5224f2d..6aa07db 100644 --- a/include/mmtf/binary_encoder.hpp +++ b/include/mmtf/binary_encoder.hpp @@ -98,21 +98,21 @@ inline std::vector encodeInt8ToByte(std::vector vec_in); * @param[in] vec_in Vector of ints to encode * @return Char vector of encoded bytes */ -inline std::vector encodeFourByteInt(std::vector vec_in); +inline std::vector encodeFourByteInt(std::vector const & vec_in); /** Encode string vector encoding (type 5) * @param[in] in_sv Vector of strings to encode * @param[in] CHAIN_LEN Maximum length of string * @return Char vector of encoded bytes */ -inline std::vector encodeStringVector(std::vector in_sv, int32_t CHAIN_LEN); +inline std::vector encodeStringVector(std::vector const & in_sv, int32_t const CHAIN_LEN); /** Encode Run Length Char encoding (type 6) * @param[in] in_cv Vector of chars to encode * @return Char vector of encoded bytes */ -inline std::vector encodeRunLengthChar(std::vector in_cv); +inline std::vector encodeRunLengthChar(std::vector const & in_cv); /** Encode Run Length Delta Int encoding (type 8) @@ -126,20 +126,20 @@ inline std::vector encodeRunLengthDeltaInt(std::vector int_vec); * @param[in] multiplier Multiplier to convert float to int * @return Char vector of encoded bytes */ -inline std::vector encodeRunLengthFloat(std::vector floats_in, int32_t multiplier); +inline std::vector encodeRunLengthFloat(std::vector const & floats_in, int32_t const multiplier); /** Encode Delta Recursive Float encoding (type 10) * @param[in] floats_in Vector of floats to encode * @param[in] multiplier Multiplier to convert float to int * @return Char vector of encoded bytes */ -inline std::vector encodeDeltaRecursiveFloat(std::vector floats_in, int32_t multiplier); +inline std::vector encodeDeltaRecursiveFloat(std::vector const & floats_in, int32_t const multiplier); /** Encode Run-Length 8bit int encoding (type 16) * @param[in] int8_vec Vector of ints to encode * @return Char vector of encoded bytes */ -inline std::vector encodeRunLengthInt8(std::vector int8_vec); +inline std::vector encodeRunLengthInt8(std::vector const & int8_vec); // ************************************************************************* // IMPLEMENTATION @@ -148,7 +148,7 @@ inline std::vector encodeRunLengthInt8(std::vector int8_vec); namespace { // private helpers inline std::vector convertFloatsToInts(std::vector const & vec_in, - int multiplier) { + int const multiplier) { std::vector vec_out; for (size_t i=0; i(round(vec_in[i]*multiplier))); @@ -242,7 +242,7 @@ inline std::vector encodeInt8ToByte(std::vector vec_in) { } -inline std::vector encodeFourByteInt(std::vector vec_in) { +inline std::vector encodeFourByteInt(std::vector const & vec_in) { std::stringstream ss; add_header(ss, vec_in.size(), 4, 0); for (size_t i=0; i encodeFourByteInt(std::vector vec_in) { } -inline std::vector encodeStringVector(std::vector in_sv, int32_t CHAIN_LEN) { +inline std::vector encodeStringVector(std::vector const & in_sv, int32_t const CHAIN_LEN) { char NULL_BYTE = 0x00; std::stringstream ss; add_header(ss, in_sv.size(), 5, CHAIN_LEN); @@ -271,7 +271,7 @@ inline std::vector encodeStringVector(std::vector in_sv, int3 } -inline std::vector encodeRunLengthChar(std::vector in_cv) { +inline std::vector encodeRunLengthChar(std::vector const & in_cv) { std::stringstream ss; add_header(ss, in_cv.size(), 6, 0); std::vector int_vec = runLengthEncode(in_cv); @@ -295,7 +295,7 @@ inline std::vector encodeRunLengthDeltaInt(std::vector int_vec) { return stringstreamToCharVector(ss); } -inline std::vector encodeRunLengthFloat(std::vector floats_in, int32_t multiplier) { +inline std::vector encodeRunLengthFloat(std::vector const & floats_in, int32_t const multiplier) { std::stringstream ss; add_header(ss, floats_in.size(), 9, multiplier); std::vector int_vec = convertFloatsToInts(floats_in, multiplier); @@ -308,7 +308,7 @@ inline std::vector encodeRunLengthFloat(std::vector floats_in, int3 } -inline std::vector encodeDeltaRecursiveFloat(std::vector floats_in, int32_t multiplier) { +inline std::vector encodeDeltaRecursiveFloat(std::vector const & floats_in, int32_t const multiplier) { std::stringstream ss; add_header(ss, floats_in.size(), 10, multiplier); std::vector int_vec = convertFloatsToInts(floats_in, multiplier); @@ -322,10 +322,10 @@ inline std::vector encodeDeltaRecursiveFloat(std::vector floats_in, } -inline std::vector encodeRunLengthInt8(std::vector int8_vec) { +inline std::vector encodeRunLengthInt8(std::vector const & int8_vec) { std::stringstream ss; add_header(ss, int8_vec.size(), 16, 0); - std::vector int_vec = runLengthEncode(int8_vec); + std::vector const int_vec = runLengthEncode(int8_vec); for (size_t i=0; i(&temp), sizeof(temp)); diff --git a/include/mmtf/map_decoder.hpp b/include/mmtf/map_decoder.hpp index ba77c44..33bee28 100644 --- a/include/mmtf/map_decoder.hpp +++ b/include/mmtf/map_decoder.hpp @@ -243,7 +243,7 @@ inline void MapDecoder::init_from_msgpack_obj(const msgpack::object& obj) { inline void MapDecoder::checkType_(const std::string& key, msgpack::type::object_type type, - const float& target) const { + const float&) const { if (type != msgpack::type::FLOAT32 && type != msgpack::type::FLOAT64) { std::cerr << "Warning: Non-float type " << type << " found for " "entry " << key << std::endl; @@ -251,7 +251,7 @@ inline void MapDecoder::checkType_(const std::string& key, } inline void MapDecoder::checkType_(const std::string& key, msgpack::type::object_type type, - const int32_t& target) const { + const int32_t&) const { if ( type != msgpack::type::POSITIVE_INTEGER && type != msgpack::type::NEGATIVE_INTEGER) { std::cerr << "Warning: Non-int type " << type << " found for " @@ -260,7 +260,7 @@ inline void MapDecoder::checkType_(const std::string& key, } inline void MapDecoder::checkType_(const std::string& key, msgpack::type::object_type type, - const char& target) const { + const char&) const { if (type != msgpack::type::STR) { std::cerr << "Warning: Non-string type " << type << " found for " "entry " << key << std::endl; @@ -268,7 +268,7 @@ inline void MapDecoder::checkType_(const std::string& key, } inline void MapDecoder::checkType_(const std::string& key, msgpack::type::object_type type, - const std::string& target) const { + const std::string&) const { if (type != msgpack::type::STR) { std::cerr << "Warning: Non-string type " << type << " found for " "entry " << key << std::endl; @@ -278,7 +278,7 @@ inline void MapDecoder::checkType_(const std::string& key, template void MapDecoder::checkType_(const std::string& key, msgpack::type::object_type type, - const std::vector& target) const { + const std::vector&) const { if (type != msgpack::type::ARRAY && type != msgpack::type::BIN) { std::cerr << "Warning: Non-array type " << type << " found for " "entry " << key << std::endl; @@ -287,9 +287,9 @@ void MapDecoder::checkType_(const std::string& key, template -void MapDecoder::checkType_(const std::string& key, - msgpack::type::object_type type, - const T & target) const { +void MapDecoder::checkType_(const std::string&, + msgpack::type::object_type, + const T &) const { // Do nothing -- allow all through } diff --git a/include/mmtf/structure_data.hpp b/include/mmtf/structure_data.hpp index 0d37e32..c16cfe6 100644 --- a/include/mmtf/structure_data.hpp +++ b/include/mmtf/structure_data.hpp @@ -163,7 +163,7 @@ struct StructureData { std::string title; std::string depositionDate; std::string releaseDate; - std::vector > ncsOperatorList; + std::vector> ncsOperatorList; std::vector bioAssemblyList; std::vector entityList; std::vector experimentalMethods; diff --git a/mmtf_spec b/mmtf_spec deleted file mode 160000 index 8c88834..0000000 --- a/mmtf_spec +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8c8883457e54fb460908a57d801212c56a603aec diff --git a/msgpack-c b/msgpack-c deleted file mode 160000 index 7a98138..0000000 --- a/msgpack-c +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7a98138f27f27290e680bf8fbf1f8d1b089bf138 diff --git a/pybind11 b/pybind11 deleted file mode 160000 index f6c4c10..0000000 --- a/pybind11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f6c4c1047a293ea77223c21f84f888e062212d78 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..868189f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,85 @@ +[project] +name = "mmtf_cppy" +version = "1.1.1" +description="A minimal example package (with pybind11)" +readme = "README.md" +url = "https://github.com/rcsb/mmtf-cpp" +authors = [ + { name = "Danny Farrell", email = "16297104+danpf@users.noreply.github.com" }, +] + +requires-python = ">=3.8" +classifiers = [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + +dependencies = [ + "numpy", + "msgpack" +] + +[build-system] +requires = ["scikit-build-core>=0.3.3", "pybind11"] +build-backend = "scikit_build_core.build" + +[tool.scikit-build] +wheel.expand-macos-universal-tags = true +cmake.minimum-version = "3.15" +cmake.build-type = "Release" +cmake.source-dir = "." +wheel.packages = ["src/python/mmtf_cppy"] +# Uncomment during development +build-dir = "build/{wheel_tag}" + +[tool.scikit-build.cmake.define] +build_py = "ON" +MSGPACK_USE_BOOST = "OFF" + + +[tool.cibuildwheel] +test-command = "python {project}/src/python/tests/tests.py" +test-skip = ["*universal2:arm64"] +build-verbosity = 1 + + +[tool.ruff] +src = ["src/python"] + +[tool.ruff.lint] +extend-select = [ + "B", # flake8-bugbear + "I", # isort + "ARG", # flake8-unused-arguments + "C4", # flake8-comprehensions + "EM", # flake8-errmsg + "ICN", # flake8-import-conventions + "G", # flake8-logging-format + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "RET", # flake8-return + "RUF", # Ruff-specific + "SIM", # flake8-simplify + "T20", # flake8-print + "UP", # pyupgrade + "YTT", # flake8-2020 + "EXE", # flake8-executable + "NPY", # NumPy specific rules + "PD", # pandas-vet +] +ignore = [ + "PLR", # Design related pylint codes +] +isort.required-imports = ["from __future__ import annotations"] + +[tool.ruff.per-file-ignores] +"tests/**" = ["T20"] + diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt new file mode 100644 index 0000000..83ee2d1 --- /dev/null +++ b/src/python/CMakeLists.txt @@ -0,0 +1,21 @@ +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +FetchContent_Declare( + pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11.git + GIT_TAG v2.11.1) +FetchContent_MakeAvailable(pybind11) +find_package(pybind11 CONFIG REQUIRED) + +find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) + +Python_add_library(mmtf_bindings MODULE bindings.cpp WITH_SOABI) + +target_include_directories(mmtf_bindings PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../../include +) +target_link_libraries(mmtf_bindings PRIVATE pybind11::headers MMTFcpp) +target_compile_definitions(mmtf_bindings PRIVATE VERSION_INFO=${SKBUILD_PROJECT_VERSION}) +install(TARGETS mmtf_bindings DESTINATION mmtf_cppy) diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp new file mode 100644 index 0000000..55914d2 --- /dev/null +++ b/src/python/bindings.cpp @@ -0,0 +1,515 @@ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace py = pybind11; + +/// CPP -> PY FUNCTIONS + +/* Notes + * We destory original data because it is much faster to apply move + * than it is to copy the data. + */ + +// This destroys the original data +template< typename T > +py::array +array1d_from_vector(std::vector & m) { + if (m.empty()) return py::array_t(); + std::vector* ptr = new std::vector(std::move(m)); + auto capsule = py::capsule(ptr, [](void* p) { + delete reinterpret_cast*>(p); + }); + return py::array_t( + ptr->size(), // shape of array + ptr->data(), // c-style contiguous strides for Sequence + capsule // numpy array references this parent + ); +} + + +template<> +py::array +array1d_from_vector(std::vector & m) { + //if (m.empty()) return py::array_t(); + std::vector* ptr = new std::vector(std::move(m)); + auto capsule = py::capsule(ptr, [](void* p) { + delete reinterpret_cast*>(p); + }); + return py::array( + py::dtype("size()}, // shape of array + {}, + ptr->data(), // c-style contiguous strides for Sequence + capsule // numpy array references this parent + ); +} + +template< > +py::array +array1d_from_vector(std::vector & m) { + return py::array(py::cast(std::move(m))); +} + +template +std::vector +flatten2D(std::vector> const & v) { + std::size_t total_size = 0; + for (auto const & x : v) + total_size += x.size(); + std::vector result; + result.reserve(total_size); + for (auto const & subv : v) + result.insert(result.end(), subv.begin(), subv.end()); + return result; +} + + +// would be nice if this was faster +template< typename T > +py::array +array2D_from_vector(std::vector> const & m) { + if (m.empty()) return py::array_t(); + std::vector* ptr = new std::vector(flatten2D(m)); + auto capsule = py::capsule(ptr, [](void* p) { + delete reinterpret_cast*>(p); + }); + return py::array_t( + {m.size(), m.at(0).size()}, // shape of array + {m.at(0).size()*sizeof(T), sizeof(T)}, // c-style contiguous strides + ptr->data(), + capsule); +} + +// This destroys the original data +py::list +dump_bio_assembly_list(mmtf::StructureData & sd) { + py::object py_ba_class = py::module::import("mmtf_cppy").attr("BioAssembly"); + py::object py_t_class = py::module::import("mmtf_cppy").attr("Transform"); + py::list bal; + for (mmtf::BioAssembly & cba : sd.bioAssemblyList) { + py::list transform_list; + for (mmtf::Transform & trans : cba.transformList) { + std::vector matrix(std::begin(trans.matrix), std::end(trans.matrix)); + transform_list.append( + py_t_class( + array1d_from_vector(trans.chainIndexList), + array1d_from_vector(matrix) + ) + ); + } + bal.append( + py_ba_class( + transform_list, + py::str(cba.name) + ) + ); + } + return bal; +} + +// This destroys the original data +py::list +dump_entity_list(std::vector & cpp_el) { + py::object entity = py::module::import("mmtf_cppy").attr("Entity"); + py::list el; + for (mmtf::Entity & e : cpp_el) { + el.append( + entity( + array1d_from_vector(e.chainIndexList), + e.description, + e.type, + e.sequence) + ); + } + return el; +} + +py::bytes +raw_properties(mmtf::StructureData const & sd) { + std::stringstream bytes; + std::map< std::string, std::map< std::string, msgpack::object > > objs({ + {"bondProperties", sd.bondProperties}, + {"atomProperties", sd.atomProperties}, + {"groupProperties", sd.groupProperties}, + {"chainProperties", sd.chainProperties}, + {"modelProperties", sd.modelProperties}, + {"extraProperties", sd.extraProperties}}); + msgpack::pack(bytes, objs); + return py::bytes(bytes.str()); +} + + +std::vector +make_transformList(py::list const & l) { + std::vector tl; + for (auto const & trans : l) { + mmtf::Transform t; + t.chainIndexList = trans.attr("chainIndexList").cast>(); + py::list pymatrix(trans.attr("matrix")); + std::size_t count(0); + for (auto const & x : pymatrix) { + t.matrix[count] = x.cast(); + ++count; + } + tl.push_back(t); + } + return tl; +} + + +void +set_bioAssemblyList(py::list const & obj, mmtf::StructureData & sd) { + std::vector bioAs; + for (auto const & py_bioAssembly : obj ) { + mmtf::BioAssembly bioA; + bioA.name = py::str(py_bioAssembly.attr("name")); + py::list py_transform_list(py_bioAssembly.attr("transformList")); + std::vector transform_list = make_transformList(py_transform_list); + bioA.transformList = transform_list; + bioAs.push_back(bioA); + } + sd.bioAssemblyList = bioAs; +} + + +void +set_entityList(py::list const & obj, mmtf::StructureData & sd) { + std::vector entities; + for (auto const & py_entity : obj ) { + mmtf::Entity entity; + entity.chainIndexList = py_entity.attr("chainIndexList").cast>(); + entity.description = py_entity.attr("description").cast(); + entity.type = py_entity.attr("type").cast(); + entity.sequence = py_entity.attr("sequence").cast(); + entities.push_back(entity); + } + sd.entityList = entities; +} + + +void +set_groupList(py::list const & obj, mmtf::StructureData & sd) { + std::vector groups; + for (auto const & py_group : obj ) { + mmtf::GroupType group; + group.formalChargeList = py_group.attr("formalChargeList").cast>(); + group.atomNameList = py_group.attr("atomNameList").cast>(); + group.elementList = py_group.attr("elementList").cast>(); + group.bondAtomList = py_group.attr("bondAtomList").cast>(); + group.bondOrderList = py_group.attr("bondOrderList").cast>(); + group.bondResonanceList = py_group.attr("bondResonanceList").cast>(); + group.groupName = py_group.attr("groupName").cast(); + group.singleLetterCode = py_group.attr("singleLetterCode").cast(); + group.chemCompType = py_group.attr("chemCompType").cast(); + groups.push_back(group); + } + sd.groupList = groups; +} + + +// This destroys the original data +py::list +dump_group_list(std::vector & gtl) { + py::object py_gt_class = py::module::import("mmtf_cppy").attr("GroupType"); + py::list gl; + for (mmtf::GroupType & gt : gtl) { + gl.append( + py_gt_class( + array1d_from_vector(gt.formalChargeList), + gt.atomNameList, + gt.elementList, + array1d_from_vector(gt.bondAtomList), + array1d_from_vector(gt.bondOrderList), + array1d_from_vector(gt.bondResonanceList), + gt.groupName, + std::string(1, gt.singleLetterCode), + gt.chemCompType + ) + ); + } + return gl; +} + +template< typename T> +std::vector +py_array_to_vector(py::array_t const & array_in) { + std::vector vec_array(array_in.size()); + std::memcpy(vec_array.data(), array_in.data(), array_in.size()*sizeof(T)); + return vec_array; +} + +template<> +std::vector +py_array_to_vector(py::array_t const & array_in) { + std::string tmpstr(array_in.data(), array_in.size()); + std::vector vec_array(tmpstr.begin(), tmpstr.end()); + return vec_array; +} + +/* This isn't really necessary, but lets make the interface anyway + */ +py::bytes +py_encodeInt8ToByte(py::array_t const & array_in) { + std::vector cpp_vec(mmtf::encodeInt8ToByte(py_array_to_vector(array_in))); + return py::bytes(std::string(cpp_vec.begin(), cpp_vec.end())); +} + +py::bytes +py_encodeFourByteInt(py::array_t const & array_in) { + std::vector const cpp_vec(py_array_to_vector(array_in)); + std::vector encoded(mmtf::encodeFourByteInt(cpp_vec)); + return py::bytes(encoded.data(), encoded.size()); +} + +py::bytes +py_encodeRunLengthChar(py::array_t const & array_in) { + std::vector const cpp_vec(py_array_to_vector(array_in)); + std::vector encoded(mmtf::encodeRunLengthChar(cpp_vec)); + return py::bytes(encoded.data(), encoded.size()); +} + +py::bytes +py_encodeRunLengthDeltaInt(py::array_t const & array_in) { + std::vector const cpp_vec(py_array_to_vector(array_in)); + std::vector encoded(mmtf::encodeRunLengthDeltaInt(cpp_vec)); + return py::bytes(encoded.data(), encoded.size()); +} + +py::bytes +py_encodeDeltaRecursiveFloat(py::array_t const & array_in, int32_t const multiplier = 1000) { + std::vector const cpp_vec(py_array_to_vector(array_in)); + std::vector encoded(mmtf::encodeDeltaRecursiveFloat(cpp_vec, multiplier)); + return py::bytes(encoded.data(), encoded.size()); +} + +py::bytes +py_encodeRunLengthFloat(py::array_t const & array_in, int32_t const multiplier = 1000) { + std::vector const cpp_vec(py_array_to_vector(array_in)); + std::vector encoded(mmtf::encodeRunLengthFloat(cpp_vec, multiplier)); + return py::bytes(encoded.data(), encoded.size()); +} + + + +py::bytes +py_encodeRunLengthInt8(py::array_t const & array_in) { + std::vector const cpp_vec(py_array_to_vector(array_in)); + std::vector encoded(mmtf::encodeRunLengthInt8(cpp_vec)); + return py::bytes(encoded.data(), encoded.size()); +} + +// TODO pyarray POD types to numpy array? seems hard +//py::bytes +//py_encodeStringVector4(py::array_t> const & array_in) { +// using np_str_t = std::array; +// pybind11::array_t cstring_array(vector.size()); +// const char * data = +// np_str_t* array_of_cstr_ptr = reinterpret_cast(cstring_array.request().ptr); +// +// +///* std::vector tobuild; */ +///* std::vector const cpp_vec(py_array_to_vector(array_in)); */ +///* std::vector encoded(mmtf::encodeStringVector(cpp_vec, max_string_size)); */ +///* return py::bytes(encoded.data(), encoded.size()); */ +//} + + +std::vector +char_vector_to_string_vector(std::vector const & cvec) { + std::vector ret(cvec.size()); + for (std::size_t i=0; i > tmp_target; + tmp_object.convert(tmp_target); + sd.bondProperties = tmp_target["bondProperties"]; + sd.atomProperties = tmp_target["atomProperties"]; + sd.groupProperties = tmp_target["groupProperties"]; + sd.chainProperties = tmp_target["chainProperties"]; + sd.modelProperties = tmp_target["modelProperties"]; + sd.extraProperties = tmp_target["extraProperties"]; +} + + +py::array +binary_decode_int32(py::bytes const & bytes) { + using namespace pybind11::literals; + std::string const tmpstr(bytes); + std::vector tmp; + mmtf::BinaryDecoder bd(tmpstr); + bd.decode(tmp); + return array1d_from_vector(tmp); +} + +py::array +binary_decode_int16(py::bytes const & bytes) { + using namespace pybind11::literals; + std::string const tmpstr(bytes); + std::vector tmp; + mmtf::BinaryDecoder bd(tmpstr); + bd.decode(tmp); + return array1d_from_vector(tmp); +} + +py::array +binary_decode_int8(py::bytes const & bytes) { + using namespace pybind11::literals; + std::string const tmpstr(bytes); + std::vector tmp; + mmtf::BinaryDecoder bd(tmpstr); + bd.decode(tmp); + return array1d_from_vector(tmp); +} + + +py::array +binary_decode_char(py::bytes const & bytes) { + using namespace pybind11::literals; + std::string const tmpstr(bytes); + std::vector tmp; + mmtf::BinaryDecoder bd(tmpstr); + bd.decode(tmp); + return array1d_from_vector(tmp); +} + +py::array +binary_decode_float(py::bytes const & bytes) { + using namespace pybind11::literals; + std::string const tmpstr(bytes); + std::vector tmp; + mmtf::BinaryDecoder bd(tmpstr); + bd.decode(tmp); + return array1d_from_vector(tmp); +} + + +PYBIND11_MODULE(mmtf_bindings, m) { + m.def("decode_int32", &binary_decode_int32, "decode array[int32_t]"); + m.def("decode_int16", &binary_decode_int16, "decode array[int16_t]"); + m.def("decode_int8", &binary_decode_int8, "decode array[int8_t]"); + m.def("decode_char", &binary_decode_char, "decode array[char]"); + m.def("decode_float", &binary_decode_float, "decode array[float]"); + // new stuff here + py::class_(m, "CPPStructureData") + .def( pybind11::init( [](){ return new mmtf::StructureData(); } ) ) + .def( pybind11::init( [](mmtf::StructureData const &o){ return new mmtf::StructureData(o); } ) ) + .def_readwrite("mmtfVersion", &mmtf::StructureData::mmtfVersion) + .def_readwrite("mmtfProducer", &mmtf::StructureData::mmtfProducer) + .def("unitCell", [](mmtf::StructureData &m){return array1d_from_vector(m.unitCell);}) + .def_readwrite("unitCell_io", &mmtf::StructureData::unitCell) + .def_readwrite("spaceGroup", &mmtf::StructureData::spaceGroup) + .def_readwrite("structureId", &mmtf::StructureData::structureId) + .def_readwrite("title", &mmtf::StructureData::title) + .def_readwrite("depositionDate", &mmtf::StructureData::depositionDate) + .def_readwrite("releaseDate", &mmtf::StructureData::releaseDate) + .def("ncsOperatorList", [](mmtf::StructureData &m){return array2D_from_vector(m.ncsOperatorList);}) + .def_readwrite("ncsOperatorList_io", &mmtf::StructureData::ncsOperatorList, py::return_value_policy::move) + .def("bioAssemblyList", [](mmtf::StructureData &m){return dump_bio_assembly_list(m);}) + .def_readwrite("bioAssemblyList_io", &mmtf::StructureData::bioAssemblyList) + .def("entityList", [](mmtf::StructureData &m){return dump_entity_list(m.entityList);}) + .def_readwrite("entityList_io", &mmtf::StructureData::entityList) + .def_readwrite("experimentalMethods", &mmtf::StructureData::experimentalMethods) + .def_readwrite("resolution", &mmtf::StructureData::resolution) + .def_readwrite("rFree", &mmtf::StructureData::rFree) + .def_readwrite("rWork", &mmtf::StructureData::rWork) + .def_readwrite("numBonds", &mmtf::StructureData::numBonds) + .def_readwrite("numAtoms", &mmtf::StructureData::numAtoms) + .def_readwrite("numGroups", &mmtf::StructureData::numGroups) + .def_readwrite("numChains", &mmtf::StructureData::numChains) + .def_readwrite("numModels", &mmtf::StructureData::numModels) + .def("groupList", [](mmtf::StructureData &m){return dump_group_list(m.groupList);}) + .def_readwrite("groupList_io", &mmtf::StructureData::groupList) + .def("unitCell", [](mmtf::StructureData &m){return array1d_from_vector(m.unitCell);}) + .def_readwrite("unitCell_io", &mmtf::StructureData::unitCell) + .def("bondAtomList", [](mmtf::StructureData &m){return array1d_from_vector(m.bondAtomList);}) + .def_readwrite("bondAtomList_io", &mmtf::StructureData::bondAtomList) + .def("bondOrderList", [](mmtf::StructureData &m){return array1d_from_vector(m.bondOrderList);}) + .def_readwrite("bondOrderList_io", &mmtf::StructureData::bondOrderList) + .def("bondResonanceList", [](mmtf::StructureData &m){return array1d_from_vector(m.bondResonanceList);}) + .def_readwrite("bondResonanceList_io", &mmtf::StructureData::bondResonanceList) + .def("xCoordList", [](mmtf::StructureData &m){return array1d_from_vector(m.xCoordList);}) + .def_readwrite("xCoordList_io", &mmtf::StructureData::xCoordList) + .def("yCoordList", [](mmtf::StructureData &m){return array1d_from_vector(m.yCoordList);}) + .def_readwrite("yCoordList_io", &mmtf::StructureData::yCoordList) + .def("zCoordList", [](mmtf::StructureData &m){return array1d_from_vector(m.zCoordList);}) + .def_readwrite("zCoordList_io", &mmtf::StructureData::zCoordList) + .def("bFactorList", [](mmtf::StructureData &m){return array1d_from_vector(m.bFactorList);}) + .def_readwrite("bFactorList_io", &mmtf::StructureData::bFactorList) + .def("atomIdList", [](mmtf::StructureData &m){return array1d_from_vector(m.atomIdList);}) + .def_readwrite("atomIdList_io", &mmtf::StructureData::atomIdList) + .def("altLocList", [](mmtf::StructureData &m) { + return array1d_from_vector(m.altLocList); + /* std::vector tmp(char_vector_to_string_vector(m.altLocList)); */ + /* return array1d_from_vector(tmp); */ + }) + .def("set_altLocList", [](mmtf::StructureData &m, py::array_t const & st) { + m.altLocList = std::vector(st.data(), st.data()+st.size()); + }) + .def("occupancyList", [](mmtf::StructureData &m){return array1d_from_vector(m.occupancyList);}) + .def_readwrite("occupancyList_io", &mmtf::StructureData::occupancyList) + .def("groupIdList", [](mmtf::StructureData &m){return array1d_from_vector(m.groupIdList);}) + .def_readwrite("groupIdList_io", &mmtf::StructureData::groupIdList) + .def("groupTypeList", [](mmtf::StructureData &m){return array1d_from_vector(m.groupTypeList);}) + .def_readwrite("groupTypeList_io", &mmtf::StructureData::groupTypeList) + .def("secStructList", [](mmtf::StructureData &m){return array1d_from_vector(m.secStructList);}) + .def_readwrite("secStructList_io", &mmtf::StructureData::secStructList) + .def("insCodeList", [](mmtf::StructureData &m) { + std::vector tmp(char_vector_to_string_vector(m.insCodeList)); + return array1d_from_vector(tmp); + }) + .def("set_insCodeList", [](mmtf::StructureData &m, py::array_t const & st) { + m.insCodeList = std::vector(st.data(), st.data()+st.size()); + }) + .def("sequenceIndexList", [](mmtf::StructureData &m){return array1d_from_vector(m.sequenceIndexList);}) + .def_readwrite("sequenceIndexList_io", &mmtf::StructureData::sequenceIndexList) + .def("chainIdList", [](mmtf::StructureData &m){return array1d_from_vector(m.chainIdList);}) + .def_readwrite("chainIdList_io", &mmtf::StructureData::chainIdList) + .def("chainNameList", [](mmtf::StructureData &m){return array1d_from_vector(m.chainNameList);}) + .def_readwrite("chainNameList_io", &mmtf::StructureData::chainNameList) + .def("groupsPerChain", [](mmtf::StructureData &m){return array1d_from_vector(m.groupsPerChain);}) + .def_readwrite("groupsPerChain_io", &mmtf::StructureData::groupsPerChain) + .def("chainsPerModel", [](mmtf::StructureData &m){return array1d_from_vector(m.chainsPerModel);}) + .def_readwrite("chainsPerModel_io", &mmtf::StructureData::chainsPerModel) + .def("set_properties", [](mmtf::StructureData & sd, py::bytes const & bytes_in){set_properties(sd, bytes_in);}) + .def("raw_properties", [](mmtf::StructureData const &m){return raw_properties(m);}); + + // I think it would be ideal to not pass in the sd, but it is still very + // fast this way. + m.def("set_bioAssemblyList", [](py::list const & i, mmtf::StructureData & sd){return set_bioAssemblyList(i, sd);}); + m.def("set_entityList", [](py::list const & i, mmtf::StructureData & sd){return set_entityList(i, sd);}); + m.def("set_groupList", [](py::list const & i, mmtf::StructureData & sd){return set_groupList(i, sd);}); + m.def("decodeFromFile", &mmtf::decodeFromFile, "decode a mmtf::StructureData from a file"); + m.def("decodeFromBuffer", &mmtf::decodeFromBuffer, "decode a mmtf::StructureData from bytes"); + m.def("encodeToFile", [](mmtf::StructureData const &m, std::string const & fn){mmtf::encodeToFile(m, fn);}); + m.def("encodeToStream", [](mmtf::StructureData const &m){ + std::stringstream ss; + mmtf::encodeToStream(m, ss); + return py::bytes(ss.str()); + }); + // encoders + m.def("encodeInt8ToByte", &py_encodeInt8ToByte); + m.def("encodeFourByteInt", &py_encodeFourByteInt); + m.def("encodeRunLengthChar", &py_encodeRunLengthChar); + m.def("encodeRunLengthDeltaInt", &py_encodeRunLengthDeltaInt); + m.def("encodeDeltaRecursiveFloat", &py_encodeDeltaRecursiveFloat); + m.def("encodeRunLengthFloat", &py_encodeRunLengthFloat); + m.def("encodeRunLengthInt8", &py_encodeRunLengthInt8); + //m.def("encodeStringVector", &py_encodeStringVector); +} diff --git a/src/python/mmtf_cppy/__init__.py b/src/python/mmtf_cppy/__init__.py new file mode 100644 index 0000000..8db9e0c --- /dev/null +++ b/src/python/mmtf_cppy/__init__.py @@ -0,0 +1,31 @@ +from .structure_data import ( + Entity as Entity, + GroupType as GroupType, + Transform as Transform, + BioAssembly as BioAssembly, + StructureData as StructureData, +) + + +from .mmtf_bindings import ( + CPPStructureData as CPPStructureData, + decode_int8 as decode_int8, + encodeRunLengthInt8 as encodeRunLengthInt8, + decodeFromBuffer as decodeFromBuffer, + encodeDeltaRecursiveFloat as encodeDeltaRecursiveFloat, + encodeToFile as encodeToFile, + decodeFromFile as decodeFromFile, + encodeFourByteInt as encodeFourByteInt, + encodeToStream as encodeToStream, + decode_char as decode_char, + encodeInt8ToByte as encodeInt8ToByte, + set_bioAssemblyList as set_bioAssemblyList, + decode_float as decode_float, + encodeRunLengthChar as encodeRunLengthChar, + set_entityList as set_entityList, + decode_int16 as decode_int16, + encodeRunLengthDeltaInt as encodeRunLengthDeltaInt, + set_groupList as set_groupList, + decode_int32 as decode_int32, + encodeRunLengthFloat as encodeRunLengthFloat, +) diff --git a/src/python/mmtf_cppy/structure_data.py b/src/python/mmtf_cppy/structure_data.py new file mode 100644 index 0000000..656996b --- /dev/null +++ b/src/python/mmtf_cppy/structure_data.py @@ -0,0 +1,495 @@ +from os import PathLike +from typing import Dict, List, Optional, Union + +import msgpack +import numpy as np + +from . import mmtf_bindings +from .mmtf_bindings import ( + CPPStructureData, + decodeFromBuffer, + decodeFromFile, + encodeToFile, + encodeToStream, +) + + +class Entity: + def __init__(self, chainIndexList, description, type_, sequence): + self.chainIndexList = chainIndexList + self.description = description + self.type = type_ + self.sequence = sequence + + def __repr__(self): + return ( + f"chainIndexList: {self.chainIndexList}" + f"description: {self.description}" + f"type: {self.type}" + f"sequence: {self.sequence}" + ) + + def __eq__(self, other: "Entity"): + return ( + (self.chainIndexList == other.chainIndexList).all() + and self.description == other.description + and self.type == other.type + and self.sequence == other.sequence + ) + + +class GroupType: + def __init__( + self, + formalChargeList: np.ndarray, + atomNameList, + elementList, + bondAtomList, + bondOrderList, + bondResonanceList, + groupName, + singleLetterCode, + chemCompType, + ): + self.formalChargeList = formalChargeList + self.atomNameList = atomNameList + self.elementList = elementList + self.bondAtomList = bondAtomList + self.bondOrderList = bondOrderList + self.bondResonanceList = bondResonanceList + self.groupName = groupName + self.singleLetterCode = singleLetterCode + self.chemCompType = chemCompType + + def __repr__(self): + return ( + f"formalChargeList: {self.formalChargeList}" + f" atomNameList: {self.atomNameList}" + f" elementList: {self.elementList}" + f" bondAtomList: {self.bondAtomList}" + f" bondOrderList: {self.bondOrderList}" + f" bondResonanceList: {self.bondResonanceList}" + f" groupName: {self.groupName}" + f" singleLetterCode: {self.singleLetterCode}" + f" chemCompType: {self.chemCompType}" + ) + + def __eq__(self, other: "GroupType"): + return ( + (self.formalChargeList == other.formalChargeList).all() + and self.atomNameList == other.atomNameList + and self.elementList == other.elementList + and (self.bondAtomList == other.bondAtomList).all() + and (self.bondOrderList == other.bondOrderList).all() + and (self.bondResonanceList == other.bondResonanceList).all() + and self.groupName == other.groupName + and self.singleLetterCode == other.singleLetterCode + and self.chemCompType == other.chemCompType + ) + + +class Transform: + def __init__(self, chainIndexList: np.ndarray, matrix: np.ndarray): + self.chainIndexList = chainIndexList + self.matrix = matrix + + def __eq__(self, other: "Transform"): + return (self.chainIndexList == other.chainIndexList).all() and (self.matrix == other.matrix).all() + + +class BioAssembly: + def __init__(self, transformList: List[Transform], name: str): + self.transformList = transformList + self.name = name + + def __eq__(self, other: "BioAssembly"): + return self.transformList == other.transformList and self.name == other.name + + +def cppSD_from_SD(sd: "StructureData"): + cppsd = CPPStructureData() + cppsd.mmtfVersion = sd.mmtfVersion + cppsd.mmtfProducer = sd.mmtfProducer + cppsd.unitCell_io = sd.unitCell + cppsd.spaceGroup = sd.spaceGroup + cppsd.structureId = sd.structureId + cppsd.title = sd.title + cppsd.depositionDate = sd.depositionDate + cppsd.releaseDate = sd.releaseDate + cppsd.ncsOperatorList_io = sd.ncsOperatorList + mmtf_bindings.set_bioAssemblyList(sd.bioAssemblyList, cppsd) + mmtf_bindings.set_entityList(sd.entityList, cppsd) + cppsd.experimentalMethods = sd.experimentalMethods + cppsd.resolution = sd.resolution + cppsd.rFree = sd.rFree + cppsd.rWork = sd.rWork + cppsd.numBonds = sd.numBonds + cppsd.numAtoms = sd.numAtoms + cppsd.numGroups = sd.numGroups + cppsd.numChains = sd.numChains + cppsd.numModels = sd.numModels + mmtf_bindings.set_groupList(sd.groupList, cppsd) + cppsd.bondAtomList_io = sd.bondAtomList + cppsd.bondOrderList_io = sd.bondOrderList + cppsd.bondResonanceList_io = sd.bondResonanceList + cppsd.xCoordList_io = sd.xCoordList + cppsd.yCoordList_io = sd.yCoordList + cppsd.zCoordList_io = sd.zCoordList + cppsd.bFactorList_io = sd.bFactorList + cppsd.atomIdList_io = sd.atomIdList + assert sd.altLocList is not None + tmp_altLocList = np.array([ord(x) if x else 0 for x in sd.altLocList], dtype=np.int8) + cppsd.set_altLocList(tmp_altLocList) + del tmp_altLocList + cppsd.occupancyList_io = sd.occupancyList + cppsd.groupIdList_io = sd.groupIdList + cppsd.groupTypeList_io = sd.groupTypeList + cppsd.secStructList_io = sd.secStructList + assert sd.insCodeList is not None + tmp_insCodeList = np.array([ord(x) if x else 0 for x in sd.insCodeList], dtype=np.int8) + cppsd.set_insCodeList(np.int8(tmp_insCodeList)) + del tmp_insCodeList + cppsd.sequenceIndexList_io = sd.sequenceIndexList + cppsd.chainIdList_io = sd.chainIdList + cppsd.chainNameList_io = sd.chainNameList + cppsd.groupsPerChain_io = sd.groupsPerChain + cppsd.chainsPerModel_io = sd.chainsPerModel + packed_data = msgpack.packb( + { + "bondProperties": sd.bondProperties, + "atomProperties": sd.atomProperties, + "groupProperties": sd.groupProperties, + "chainProperties": sd.chainProperties, + "modelProperties": sd.modelProperties, + "extraProperties": sd.extraProperties, + }, + use_bin_type=True, + ) + cppsd.set_properties(packed_data) + return cppsd + + +class StructureData: + def __init__( + self, + mmtfVersion=Optional[str], + mmtfProducer=Optional[str], + unitCell=Optional[List[float]], + spaceGroup=Optional[str], + structureId=Optional[str], + title=Optional[str], + depositionDate=Optional[str], + releaseDate=Optional[str], + ncsOperatorList=Optional[List[List[float]]], + bioAssemblyList=Optional[List[BioAssembly]], + entityList=Optional[List[Entity]], + experimentalMethods=Optional[str], + resolution=Optional[float], + rFree=Optional[float], + rWork=Optional[float], + numBonds=Optional[int], + numAtoms=Optional[int], + numGroups=Optional[int], + numChains=Optional[int], + numModels=Optional[int], + groupList=Optional[List[GroupType]], + bondAtomList=Optional[List[int]], + bondOrderList=Optional[List[int]], # type ? + bondResonanceList=Optional[List[int]], + xCoordList=Optional[List[float]], + yCoordList=Optional[List[float]], + zCoordList=Optional[List[float]], + bFactorList=Optional[List[float]], + atomIdList=Optional[List[int]], + altLocList=Optional[List[str]], + occupancyList=Optional[List[int]], + groupIdList=Optional[int], + groupTypeList=Optional[int], + secStructList=Optional[int], + insCodeList=Optional[str], + sequenceIndexList=Optional[int], + chainIdList=Optional[List[str]], + chainNameList=Optional[List[str]], + groupsPerChain=Optional[List[int]], + chainsPerModel=Optional[List[int]], + bondProperties=Optional[Dict[str, bytes]], + atomProperties=Optional[Dict[str, bytes]], + groupProperties=Optional[Dict[str, bytes]], + chainProperties=Optional[Dict[str, bytes]], + modelProperties=Optional[Dict[str, bytes]], + extraProperties=Optional[Dict[str, bytes]], + ): + """ + Recommended to use `StructureData.init_from_bytes` or `StructureData.init_from_file_name` + + Note: + file and bytes are separated because it will be faster + if you just let c++ handle the file (rather than have + python read the bytes itself and pass them to c++) + """ + self.mmtfVersion = mmtfVersion + self.mmtfProducer = mmtfProducer + self.unitCell = unitCell + self.spaceGroup = spaceGroup + self.structureId = structureId + self.title = title + self.depositionDate = depositionDate + self.releaseDate = releaseDate + self.ncsOperatorList = ncsOperatorList + self.bioAssemblyList = bioAssemblyList + self.entityList = entityList + self.experimentalMethods = experimentalMethods + self.resolution = resolution + self.rFree = rFree + self.rWork = rWork + self.numBonds = numBonds + self.numAtoms = numAtoms + self.numGroups = numGroups + self.numChains = numChains + self.numModels = numModels + self.groupList = groupList + self.bondAtomList = bondAtomList + self.bondOrderList = bondOrderList + self.bondResonanceList = bondResonanceList + self.xCoordList = xCoordList + self.yCoordList = yCoordList + self.zCoordList = zCoordList + self.bFactorList = bFactorList + self.atomIdList = atomIdList + self.altLocList = altLocList + self.occupancyList = occupancyList + self.groupIdList = groupIdList + self.groupTypeList = groupTypeList + self.secStructList = secStructList + self.insCodeList = insCodeList + self.sequenceIndexList = sequenceIndexList + self.chainIdList = chainIdList + self.chainNameList = chainNameList + self.groupsPerChain = groupsPerChain + self.chainsPerModel = chainsPerModel + self.bondProperties = bondProperties + self.atomProperties = atomProperties + self.groupProperties = groupProperties + self.chainProperties = chainProperties + self.modelProperties = modelProperties + self.extraProperties = extraProperties + + @classmethod + def init_from_bytes(cls, file_bytes: bytes) -> "StructureData": + cppsd = CPPStructureData() + decodeFromBuffer(cppsd, file_bytes, len(file_bytes)) + return cls.init_from_cppsd(cppsd) + + @classmethod + def init_from_file_name(cls, file_name: Union[str, PathLike]) -> "StructureData": + cppsd = CPPStructureData() + decodeFromFile(cppsd, str(file_name)) + return cls.init_from_cppsd(cppsd) + + @classmethod + def init_from_cppsd(cls, cppsd: "CPPStructureData") -> "StructureData": + raw_properties = cppsd.raw_properties() + raw_properties = msgpack.unpackb(raw_properties, raw=False) + return cls( + mmtfVersion=cppsd.mmtfVersion, + mmtfProducer=cppsd.mmtfProducer, + unitCell=cppsd.unitCell(), + spaceGroup=cppsd.spaceGroup, + structureId=cppsd.structureId, + title=cppsd.title, + depositionDate=cppsd.depositionDate, + releaseDate=cppsd.releaseDate, + ncsOperatorList=cppsd.ncsOperatorList(), + bioAssemblyList=cppsd.bioAssemblyList(), + entityList=cppsd.entityList(), + experimentalMethods=cppsd.experimentalMethods, + resolution=cppsd.resolution, + rFree=cppsd.rFree, + rWork=cppsd.rWork, + numBonds=cppsd.numBonds, + numAtoms=cppsd.numAtoms, + numGroups=cppsd.numGroups, + numChains=cppsd.numChains, + numModels=cppsd.numModels, + groupList=cppsd.groupList(), + bondAtomList=cppsd.bondAtomList(), + bondOrderList=cppsd.bondOrderList(), + bondResonanceList=cppsd.bondResonanceList(), + xCoordList=cppsd.xCoordList(), + yCoordList=cppsd.yCoordList(), + zCoordList=cppsd.zCoordList(), + bFactorList=cppsd.bFactorList(), + atomIdList=cppsd.atomIdList(), + altLocList=cppsd.altLocList(), + occupancyList=cppsd.occupancyList(), + groupIdList=cppsd.groupIdList(), + groupTypeList=cppsd.groupTypeList(), + secStructList=cppsd.secStructList(), + insCodeList=cppsd.insCodeList(), + sequenceIndexList=cppsd.sequenceIndexList(), + chainIdList=cppsd.chainIdList(), + chainNameList=cppsd.chainNameList(), + groupsPerChain=cppsd.groupsPerChain(), + chainsPerModel=cppsd.chainsPerModel(), + bondProperties=raw_properties["bondProperties"], + atomProperties=raw_properties["atomProperties"], + groupProperties=raw_properties["groupProperties"], + chainProperties=raw_properties["chainProperties"], + modelProperties=raw_properties["modelProperties"], + extraProperties=raw_properties["extraProperties"], + ) + + def write_to_file(self, filename: Union[str, PathLike]): + cppsd = cppSD_from_SD(self) + encodeToFile(cppsd, str(filename)) + + def write_to_bytes(self): + cppsd = cppSD_from_SD(self) + return encodeToStream(cppsd) + + def check_equals(self, other: "StructureData"): + """ + A debugging function, to check and see what parts of each StructureData are not identical + """ + if not (self.mmtfVersion == other.mmtfVersion): + print("NOT self.mmtfVersion == other.mmtfVersion") + if not (self.mmtfProducer == other.mmtfProducer): + print("NOT self.mmtfProducer == other.mmtfProducer") + if not ((self.unitCell == other.unitCell).all()): + print("NOT (self.unitCell == other.unitCell).all()") + if not (self.spaceGroup == other.spaceGroup): + print("NOT self.spaceGroup == other.spaceGroup") + if not (self.structureId == other.structureId): + print("NOT self.structureId == other.structureId") + if not (self.title == other.title): + print("NOT self.title == other.title") + if not (self.depositionDate == other.depositionDate): + print("NOT self.depositionDate == other.depositionDate") + if not (self.releaseDate == other.releaseDate): + print("NOT self.releaseDate == other.releaseDate") + if not ((self.ncsOperatorList == other.ncsOperatorList).all()): + print("NOT (self.ncsOperatorList == other.ncsOperatorList).all()") + if not (self.bioAssemblyList == other.bioAssemblyList): + print("NOT self.bioAssemblyList == other.bioAssemblyList") + if not (self.entityList == other.entityList): + print("NOT self.entityList == other.entityList") + if not (self.experimentalMethods == other.experimentalMethods): + print("NOT self.experimentalMethods == other.experimentalMethods") + if not (self.resolution == other.resolution): + print("NOT self.resolution == other.resolution") + if not (self.rFree == other.rFree): + print("NOT self.rFree == other.rFree") + if not (self.rWork == other.rWork): + print("NOT self.rWork == other.rWork") + if not (self.numBonds == other.numBonds): + print("NOT self.numBonds == other.numBonds") + if not (self.numAtoms == other.numAtoms): + print("NOT self.numAtoms == other.numAtoms") + if not (self.numGroups == other.numGroups): + print("NOT self.numGroups == other.numGroups") + if not (self.numChains == other.numChains): + print("NOT self.numChains == other.numChains") + if not (self.numModels == other.numModels): + print("NOT self.numModels == other.numModels") + if not (self.groupList == other.groupList): + print("NOT self.groupList == other.groupList") + if not ((self.bondAtomList == other.bondAtomList).all()): + print("NOT (self.bondAtomList == other.bondAtomList).all()") + if not ((self.bondOrderList == other.bondOrderList).all()): + print("NOT (self.bondOrderList == other.bondOrderList).all()") + if not ((self.bondResonanceList == other.bondResonanceList).all()): + print("NOT (self.bondResonanceList == other.bondResonanceList).all()") + if not ((self.xCoordList == other.xCoordList).all()): + print("NOT (self.xCoordList == other.xCoordList).all()") + if not ((self.yCoordList == other.yCoordList).all()): + print("NOT (self.yCoordList == other.yCoordList).all()") + if not ((self.zCoordList == other.zCoordList).all()): + print("NOT (self.zCoordList == other.zCoordList).all()") + if not ((self.bFactorList == other.bFactorList).all()): + print("NOT (self.bFactorList == other.bFactorList).all()") + if not ((self.atomIdList == other.atomIdList).all()): + print("NOT (self.atomIdList == other.atomIdList).all()") + if not ((self.altLocList == other.altLocList).all()): + print("NOT (self.altLocList == other.altLocList).all()") + if not ((self.occupancyList == other.occupancyList).all()): + print("NOT (self.occupancyList == other.occupancyList).all()") + if not ((self.groupIdList == other.groupIdList).all()): + print("NOT (self.groupIdList == other.groupIdList).all()") + if not ((self.groupTypeList == other.groupTypeList).all()): + print("NOT (self.groupTypeList == other.groupTypeList).all()") + if not ((self.secStructList == other.secStructList).all()): + print("NOT (self.secStructList == other.secStructList).all()") + if not ((self.insCodeList == other.insCodeList).all()): + print("NOT (self.insCodeList == other.insCodeList).all()") + if not ((self.sequenceIndexList == other.sequenceIndexList).all()): + print("NOT (self.sequenceIndexList == other.sequenceIndexList).all()") + if not ((self.chainIdList == other.chainIdList).all()): + print("NOT (self.chainIdList == other.chainIdList).all()") + if not ((self.chainNameList == other.chainNameList).all()): + print("NOT (self.chainNameList == other.chainNameList).all()") + if not ((self.groupsPerChain == other.groupsPerChain).all()): + print("NOT (self.groupsPerChain == other.groupsPerChain).all()") + if not ((self.chainsPerModel == other.chainsPerModel).all()): + print("NOT (self.chainsPerModel == other.chainsPerModel).all()") + if not (self.bondProperties == other.bondProperties): + print("NOT self.bondProperties == other.bondProperties") + if not (self.atomProperties == other.atomProperties): + print("NOT self.atomProperties == other.atomProperties") + if not (self.groupProperties == other.groupProperties): + print("NOT self.groupProperties == other.groupProperties") + if not (self.chainProperties == other.chainProperties): + print("NOT self.chainProperties == other.chainProperties") + if not (self.modelProperties == other.modelProperties): + print("NOT self.modelProperties == other.modelProperties") + if not (self.extraProperties == other.extraProperties): + print("NOT self.extraProperties == other.extraProperties") + + def __eq__(self, other: "StructureData"): + return ( + self.mmtfVersion == other.mmtfVersion + and self.mmtfProducer == other.mmtfProducer + and (self.unitCell == other.unitCell).all() + and self.spaceGroup == other.spaceGroup + and self.structureId == other.structureId + and self.title == other.title + and self.depositionDate == other.depositionDate + and self.releaseDate == other.releaseDate + and (self.ncsOperatorList == other.ncsOperatorList).all() + and self.bioAssemblyList == other.bioAssemblyList + and self.entityList == other.entityList + and self.experimentalMethods == other.experimentalMethods + and self.resolution == other.resolution + and self.rFree == other.rFree + and self.rWork == other.rWork + and self.numBonds == other.numBonds + and self.numAtoms == other.numAtoms + and self.numGroups == other.numGroups + and self.numChains == other.numChains + and self.numModels == other.numModels + and self.groupList == other.groupList + and (self.bondAtomList == other.bondAtomList).all() + and (self.bondOrderList == other.bondOrderList).all() + and (self.bondResonanceList == other.bondResonanceList).all() + and (self.xCoordList == other.xCoordList).all() + and (self.yCoordList == other.yCoordList).all() + and (self.zCoordList == other.zCoordList).all() + and (self.bFactorList == other.bFactorList).all() + and (self.atomIdList == other.atomIdList).all() + and (self.altLocList == other.altLocList).all() + and (self.occupancyList == other.occupancyList).all() + and (self.groupIdList == other.groupIdList).all() + and (self.groupTypeList == other.groupTypeList).all() + and (self.secStructList == other.secStructList).all() + and (self.insCodeList == other.insCodeList).all() + and (self.sequenceIndexList == other.sequenceIndexList).all() + and (self.chainIdList == other.chainIdList).all() + and (self.chainNameList == other.chainNameList).all() + and (self.groupsPerChain == other.groupsPerChain).all() + and (self.chainsPerModel == other.chainsPerModel).all() + and self.bondProperties == other.bondProperties + and self.atomProperties == other.atomProperties + and self.groupProperties == other.groupProperties + and self.chainProperties == other.chainProperties + and self.modelProperties == other.modelProperties + and self.extraProperties == other.extraProperties + ) diff --git a/src/python/tests/tests.py b/src/python/tests/tests.py new file mode 100644 index 0000000..c6f7e0c --- /dev/null +++ b/src/python/tests/tests.py @@ -0,0 +1,196 @@ +from pathlib import Path +import tempfile +import unittest + +import mmtf_cppy +from mmtf_cppy import StructureData +import numpy as np + +MMTF_SPEC_DIR = Path(__file__).parent / "../../../submodules/mmtf_spec" +EXTRA_TEST_DATA_DIR = Path(__file__).parent / "../../../temporary_test_data" + + +class TestMMTF(unittest.TestCase): + def setUp(self): + self.temp_dir = tempfile.TemporaryDirectory() + + def tearDown(self): + self.temp_dir.cleanup() + + def test_eq_operator(self): + s1 = StructureData.init_from_file_name(MMTF_SPEC_DIR / "test-suite/mmtf/173D.mmtf") + s2 = StructureData.init_from_file_name(MMTF_SPEC_DIR / "test-suite/mmtf/173D.mmtf") + s3 = StructureData.init_from_file_name(MMTF_SPEC_DIR / "test-suite/mmtf/1AUY.mmtf") + assert s1 == s2 + assert s1 != s3 + + def test_roundtrip(self): + files = [ + MMTF_SPEC_DIR / "test-suite/mmtf/173D.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/1AA6.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/1AUY.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/1BNA.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/1CAG.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/1HTQ.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/1IGT.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/1L2Q.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/1LPV.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/1MSH.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/1O2F.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/1R9V.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/1SKM.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/3NJW.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/3ZYB.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/4CK4.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/4CUP.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/4OPJ.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/4P3R.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/4V5A.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/4Y60.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/5EMG.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/5ESW.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/empty-all0.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/empty-numChains1.mmtf", + MMTF_SPEC_DIR / "test-suite/mmtf/empty-numModels1.mmtf", + EXTRA_TEST_DATA_DIR / "all_canoncial.mmtf", + EXTRA_TEST_DATA_DIR / "1PEF_with_resonance.mmtf", + ] + test_tmp_mmtf_filename = Path(self.temp_dir.name) / "test_mmtf.mmtf" + for filename in files: + s1 = StructureData.init_from_file_name(filename) + s1.write_to_file(test_tmp_mmtf_filename) + s2 = StructureData.init_from_file_name(test_tmp_mmtf_filename) + s1.check_equals(s2) + assert s1 == s2 + + def test_bad_mmtf(self): + doesnt_work = [MMTF_SPEC_DIR / "test-suite/mmtf/empty-mmtfVersion99999999.mmtf"] + for filename in doesnt_work: + with self.assertRaises(RuntimeError): + _ = StructureData.init_from_file_name(filename) + + def test_various_throws(self): + working_mmtf_fn = MMTF_SPEC_DIR / "test-suite/mmtf/173D.mmtf" + temporary_file = Path(self.temp_dir.name) / "wrk.mmtf" + + sd = StructureData.init_from_file_name(working_mmtf_fn) + sd.xCoordList = np.append(sd.xCoordList, 0.334) + with self.assertRaises(RuntimeError): + sd.write_to_file(temporary_file) + + sd = StructureData.init_from_file_name(working_mmtf_fn) + sd.yCoordList = np.append(sd.yCoordList, 0.334) + with self.assertRaises(RuntimeError): + sd.write_to_file(temporary_file) + + sd = StructureData.init_from_file_name(working_mmtf_fn) + sd.zCoordList = np.append(sd.zCoordList, 0.334) + with self.assertRaises(RuntimeError): + sd.write_to_file(temporary_file) + + sd = StructureData.init_from_file_name(working_mmtf_fn) + sd.bFactorList = np.append(sd.bFactorList, 0.334) + with self.assertRaises(RuntimeError): + sd.write_to_file(temporary_file) + + sd = StructureData.init_from_file_name(working_mmtf_fn) + sd.numAtoms = 20 + with self.assertRaises(RuntimeError): + sd.write_to_file(temporary_file) + + sd = StructureData.init_from_file_name(working_mmtf_fn) + sd.chainIdList = np.append(sd.chainIdList, "xsz") + with self.assertRaises(RuntimeError): + sd.write_to_file(temporary_file) + + sd = StructureData.init_from_file_name(working_mmtf_fn) + sd.chainIdList = sd.chainIdList.astype(" c++ vector string + # def test_encodeStringVector(): + # encoded_data = b'\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\x04B\x00\x00\x00A\x00\x00\x00C\x00\x00\x00A\x00\x00\x00A\x00\x00\x00A\x00\x00\x00' + # decoded_data = np.array(("B", "A", "C", "A", "A", "A")) + # ret = mmtf_cppy.encodeStringVector(decoded_data, 4) + # assert ret == encoded_data + + def test_atomProperties(self): + working_mmtf_fn = MMTF_SPEC_DIR / "test-suite/mmtf/173D.mmtf" + tmp_output_fn = Path(self.temp_dir.name) / "properties.mmtf" + sd = StructureData.init_from_file_name(working_mmtf_fn) + random_data = list(range(256)) + encoded_random_data = mmtf_cppy.encodeRunLengthDeltaInt(list(range(256))) + sd.atomProperties["256_atomColorList"] = random_data + sd.atomProperties["256_atomColorList_encoded"] = encoded_random_data + sd.write_to_file(tmp_output_fn) + sd2 = StructureData.init_from_file_name(tmp_output_fn) + assert sd2.atomProperties["256_atomColorList"] == random_data + assert sd2.atomProperties["256_atomColorList_encoded"] == encoded_random_data + assert (mmtf_cppy.decode_int32(sd2.atomProperties["256_atomColorList_encoded"]) == np.array(random_data)).all() + +if __name__ == "__main__": + unittest.main() diff --git a/submodules/mmtf_spec b/submodules/mmtf_spec new file mode 160000 index 0000000..e4aaae5 --- /dev/null +++ b/submodules/mmtf_spec @@ -0,0 +1 @@ +Subproject commit e4aaae5d2f273d073e5482db61f74ad93f6e8ab4 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 77fa418..5c1bf36 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,22 +5,24 @@ if(EMSCRIPTEN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s TOTAL_MEMORY=150994944 -s DISABLE_EXCEPTION_CATCHING=0") endif() +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.4.0) +FetchContent_MakeAvailable(Catch2) + add_executable(mmtf_tests mmtf_tests.cpp) target_compile_features(mmtf_tests PRIVATE cxx_auto_type) -if(WIN32) - target_link_libraries(mmtf_tests Catch msgpackc MMTFcpp ws2_32) +if(EMSCRIPTEN) + target_link_libraries(mmtf_tests Catch2::Catch2 MMTFcpp) else() - target_link_libraries(mmtf_tests Catch msgpackc MMTFcpp) + target_link_libraries(mmtf_tests Catch2::Catch2WithMain MMTFcpp) endif() # test for multi-linking add_executable(multi_cpp_test multi_cpp_test.cpp multi_cpp_test_helper.cpp) target_compile_features(multi_cpp_test PRIVATE cxx_auto_type) -if(WIN32) - target_link_libraries(multi_cpp_test MMTFcpp ws2_32) -else() - target_link_libraries(multi_cpp_test MMTFcpp) -endif() +target_link_libraries(multi_cpp_test MMTFcpp) set(TEST_RUNNER "none" CACHE STRING "External runner for the tests") diff --git a/tests/mmtf_tests.cpp b/tests/mmtf_tests.cpp index 163e957..993262b 100644 --- a/tests/mmtf_tests.cpp +++ b/tests/mmtf_tests.cpp @@ -1,11 +1,9 @@ #ifdef __EMSCRIPTEN__ #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -#define CATCH_CONFIG_RUNNER -#else -#define CATCH_CONFIG_MAIN #endif -#include "catch.hpp" +#include +#include #include #include @@ -17,7 +15,7 @@ template bool approx_equal_vector(const T& a, const T& b, float eps = 0.00001) { if (a.size() != b.size()) return false; for (std::size_t i=0; i < a.size(); ++i) { - if (a[i] != Approx(b[i]).margin(eps)) return false; + if (a[i] != Catch::Approx(b[i]).margin(eps)) return false; } return true; } @@ -42,7 +40,7 @@ msgpack_obj_to_map(const msgpack::object& obj) { } TEST_CASE("assignment operator") { - std::string working_mmtf = "../mmtf_spec/test-suite/mmtf/173D.mmtf"; + std::string working_mmtf = "../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf"; mmtf::StructureData sd; mmtf::decodeFromFile(sd, working_mmtf); SECTION("basic assignment operator") { @@ -78,7 +76,7 @@ TEST_CASE("assignment operator") { TEST_CASE("copy constructor") { - std::string working_mmtf = "../mmtf_spec/test-suite/mmtf/173D.mmtf"; + std::string working_mmtf = "../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf"; mmtf::StructureData sd; mmtf::decodeFromFile(sd, working_mmtf); SECTION("Basic copy constructor") { @@ -99,32 +97,32 @@ TEST_CASE("copy constructor") { // Tests for structure_data.hpp TEST_CASE("Test round trip StructureData working") { std::vector works; - works.push_back("../mmtf_spec/test-suite/mmtf/173D.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/1AA6.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/1AUY.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/1BNA.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/1CAG.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/1HTQ.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/1IGT.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/1L2Q.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/1LPV.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/1MSH.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/1O2F.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/1R9V.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/1SKM.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/3NJW.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/3ZYB.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/4CK4.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/4CUP.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/4OPJ.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/4P3R.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/4V5A.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/4Y60.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/5EMG.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/5ESW.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/empty-all0.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/empty-numChains1.mmtf"); - works.push_back("../mmtf_spec/test-suite/mmtf/empty-numModels1.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/1AA6.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/1AUY.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/1BNA.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/1CAG.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/1HTQ.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/1IGT.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/1L2Q.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/1LPV.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/1MSH.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/1O2F.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/1R9V.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/1SKM.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/3NJW.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/3ZYB.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/4CK4.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/4CUP.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/4OPJ.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/4P3R.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/4V5A.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/4Y60.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/5EMG.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/5ESW.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/empty-all0.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/empty-numChains1.mmtf"); + works.push_back("../submodules/mmtf_spec/test-suite/mmtf/empty-numModels1.mmtf"); works.push_back("../temporary_test_data/all_canoncial.mmtf"); works.push_back("../temporary_test_data/1PEF_with_resonance.mmtf"); for (size_t i=0; i doesnt_work; - doesnt_work.push_back("../mmtf_spec/test-suite/mmtf/empty-mmtfVersion99999999.mmtf"); + doesnt_work.push_back("../submodules/mmtf_spec/test-suite/mmtf/empty-mmtfVersion99999999.mmtf"); for (size_t i=0; i& sd_map, // test round trips with all extra data set TEST_CASE("extra data fields") { // Randomly chosen, small example MMTF - std::string working_mmtf = "../mmtf_spec/test-suite/mmtf/173D.mmtf"; + std::string working_mmtf = "../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf"; mmtf::StructureData sd, sd2, sd3; mmtf::decodeFromFile(sd, working_mmtf); @@ -863,7 +861,7 @@ TEST_CASE("extra data fields") { } TEST_CASE("Test export_helpers") { - std::string working_mmtf = "../mmtf_spec/test-suite/mmtf/3NJW.mmtf"; + std::string working_mmtf = "../submodules/mmtf_spec/test-suite/mmtf/3NJW.mmtf"; mmtf::StructureData sd; mmtf::decodeFromFile(sd, working_mmtf); size_t numbonds_ref = sd.numBonds; @@ -942,7 +940,7 @@ TEST_CASE("Test export_helpers") { } TEST_CASE("Test mapdecoder from raw mmtf") { - std::string working_mmtf = "../mmtf_spec/test-suite/mmtf/173D.mmtf"; + std::string working_mmtf = "../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf"; mmtf::MapDecoder md; mmtf::mapDecoderFromFile(md, working_mmtf); std::vector bonds; @@ -955,7 +953,7 @@ TEST_CASE("Test mapdecoder from raw mmtf") { TEST_CASE("Test various encode and decode options") { // fetch reference data - std::string working_mmtf = "../mmtf_spec/test-suite/mmtf/173D.mmtf"; + std::string working_mmtf = "../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf"; mmtf::StructureData sd_ref; mmtf::decodeFromFile(sd_ref, working_mmtf); // write into buffer diff --git a/tests/multi_cpp_test.cpp b/tests/multi_cpp_test.cpp index b68ee5d..1bb9900 100644 --- a/tests/multi_cpp_test.cpp +++ b/tests/multi_cpp_test.cpp @@ -9,7 +9,7 @@ int main(int argc, char** argv) { // decode MMTF file - std::string filename = "../mmtf_spec/test-suite/mmtf/173D.mmtf"; + std::string filename = "../submodules/mmtf_spec/test-suite/mmtf/173D.mmtf"; mmtf::StructureData data; read_check_file(data, filename);