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 53d34fb..0000000 Binary files a/bindings/4lgr.mmtf and /dev/null differ 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);