fastpdb
Advanced tools
| from pathlib import Path | ||
| import pytest | ||
| import fastpdb | ||
| @pytest.fixture | ||
| def pdb_file_path(): | ||
| return Path(__file__).parents[1] / "tests" / "data" / "1aki.pdb" | ||
| @pytest.fixture | ||
| def pdb_file(pdb_file_path): | ||
| return fastpdb.PDBFile.read(pdb_file_path) |
| import pytest | ||
| import fastpdb | ||
| @pytest.mark.benchmark | ||
| def test_read(pdb_file_path): | ||
| fastpdb.PDBFile.read(pdb_file_path) | ||
| @pytest.mark.benchmark | ||
| def test_get_coord(pdb_file): | ||
| pdb_file.get_coord(model=1) | ||
| @pytest.mark.benchmark | ||
| def test_get_structure(pdb_file): | ||
| pdb_file.get_structure(model=1) | ||
| @pytest.mark.benchmark | ||
| def test_get_structure_with_bonds(pdb_file): | ||
| pdb_file.get_structure(model=1, include_bonds=True) | ||
| @pytest.mark.benchmark | ||
| def test_get_remark(pdb_file): | ||
| pdb_file.get_remark(350) |
| import pytest | ||
| import fastpdb | ||
| @pytest.fixture | ||
| def atoms(pdb_file): | ||
| return pdb_file.get_structure(model=1, include_bonds=True) | ||
| @pytest.fixture | ||
| def empty_pdb_file(): | ||
| return fastpdb.PDBFile() | ||
| @pytest.mark.benchmark | ||
| def test_set_structure(atoms, empty_pdb_file): | ||
| atoms.bonds = None | ||
| empty_pdb_file.set_structure(atoms) | ||
| @pytest.mark.benchmark | ||
| def test_set_structure_with_bonds(atoms, empty_pdb_file): | ||
| empty_pdb_file.set_structure(atoms) |
@@ -6,7 +6,12 @@ --- | ||
| workflow_dispatch: | ||
| push: | ||
| branches: | ||
| - "main" | ||
| pull_request: | ||
| release: | ||
| types: | ||
| - published | ||
| - published | ||
| env: | ||
| MATURIN_VERSION: "1.8" | ||
@@ -19,4 +24,4 @@ jobs: | ||
| # Support both Mac x86_64 and arm64 | ||
| os: [ubuntu-latest, windows-latest, macos-12, macos-latest] | ||
| py-version: ["3.10", "3.11", "3.12"] | ||
| os: [ubuntu-latest, windows-latest, macos-latest] | ||
| py-version: ["3.10", "3.11", "3.12", "3.13"] | ||
| runs-on: ${{ matrix.os }} | ||
@@ -34,6 +39,6 @@ defaults: | ||
| with: | ||
| toolchain: stable | ||
| override: true | ||
| toolchain: stable | ||
| override: true | ||
| - name: Install dependencies | ||
| run: pip install "maturin==1.4" "oldest-supported-numpy" pytest | ||
| run: pip install "maturin==$MATURIN_VERSION" "oldest-supported-numpy" pytest | ||
| - name: Build wheel | ||
@@ -44,8 +49,8 @@ run: maturin build --release -i python -o dist | ||
| - name: Test wheel | ||
| run: pytest --assert=plain | ||
| - uses: actions/upload-artifact@v3 | ||
| run: pytest --assert=plain tests | ||
| - uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: artifact-wheel-${{ matrix.os }}-${{ matrix.py-version }} | ||
| path: .//dist//*.whl | ||
| sdist: | ||
@@ -61,5 +66,5 @@ name: Build & test source distribution | ||
| with: | ||
| python-version: "3.12" | ||
| python-version: "3.13" | ||
| - name: Install dependencies | ||
| run: pip install "maturin==1.4" pytest | ||
| run: pip install "maturin==$MATURIN_VERSION" pytest | ||
| - name: Build source distribution | ||
@@ -70,7 +75,22 @@ run: maturin sdist -o dist | ||
| - name: Test source distribution | ||
| run: pytest --assert=plain | ||
| - uses: actions/upload-artifact@v3 | ||
| run: pytest --assert=plain tests | ||
| - uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: artifact-tar-${{ matrix.os }}-${{ matrix.py-version }} | ||
| path: dist//*.tar.gz | ||
| benchmarks: | ||
| runs-on: ubuntu-latest | ||
| if: github.event_name != 'release' | ||
| steps: | ||
| - uses: actions/checkout@v3 | ||
| - uses: actions/setup-python@v3 | ||
| with: | ||
| python-version: "3.13" | ||
| - name: Install dependencies | ||
| run: pip install .[test] | ||
| - name: Run benchmarks | ||
| uses: CodSpeedHQ/action@v3 | ||
| with: | ||
| run: pytest --codspeed benchmarks | ||
@@ -81,20 +101,24 @@ upload: | ||
| contents: write | ||
| needs: [wheels, sdist] | ||
| needs: | ||
| - wheels | ||
| - sdist | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/download-artifact@v3 | ||
| with: | ||
| name: artifact | ||
| path: dist | ||
| - name: List distributions to be uploaded | ||
| run: ls dist | ||
| - name: Upload to GitHub Releases | ||
| uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 | ||
| if: github.event_name == 'release' && github.event.action == 'published' | ||
| with: | ||
| files: dist//* | ||
| - name: Upload to PyPI | ||
| uses: pypa/gh-action-pypi-publish@c7f29f7adef1a245bd91520e94867e5c6eedddcc | ||
| if: github.event_name == 'release' && github.event.action == 'published' | ||
| with: | ||
| password: ${{ secrets.PYPI_TOKEN }} | ||
| - name: Download Artifacts | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| pattern: artifact-* | ||
| merge-multiple: true | ||
| path: dist | ||
| - name: List distributions to be uploaded | ||
| run: ls dist | ||
| - name: Upload to GitHub Releases | ||
| uses: softprops/action-gh-release@v2.0.5 | ||
| if: github.event_name == 'release' && github.event.action == 'published' | ||
| with: | ||
| files: dist//* | ||
| - name: Upload to PyPI | ||
| uses: pypa/gh-action-pypi-publish@release/v1 | ||
| if: github.event_name == 'release' && github.event.action == 'published' | ||
| with: | ||
| password: ${{ secrets.PYPI_TOKEN }} |
+2
-2
@@ -8,4 +8,4 @@ import time | ||
| import biotite.database.rcsb as rcsb | ||
| import biotite.structure.info as info | ||
| import biotite.structure.io.pdb as pdb | ||
| from biotite.structure.info.ccd import get_ccd | ||
| import fastpdb | ||
@@ -21,3 +21,3 @@ | ||
| # to avoid a bias due to the initial loading time | ||
| info.bond_dataset() | ||
| get_ccd() | ||
@@ -24,0 +24,0 @@ |
+55
-167
| # This file is automatically @generated by Cargo. | ||
| # It is not intended for manual editing. | ||
| version = 3 | ||
| version = 4 | ||
| [[package]] | ||
| name = "autocfg" | ||
| version = "1.3.0" | ||
| version = "1.4.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" | ||
| checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" | ||
| [[package]] | ||
| name = "bitflags" | ||
| version = "2.5.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" | ||
| [[package]] | ||
| name = "cfg-if" | ||
@@ -25,3 +19,3 @@ version = "1.0.0" | ||
| name = "fastpdb" | ||
| version = "1.3.1" | ||
| version = "1.3.2" | ||
| dependencies = [ | ||
@@ -35,33 +29,23 @@ "ndarray", | ||
| name = "heck" | ||
| version = "0.4.1" | ||
| version = "0.5.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" | ||
| checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | ||
| [[package]] | ||
| name = "indoc" | ||
| version = "2.0.5" | ||
| version = "2.0.6" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" | ||
| checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" | ||
| [[package]] | ||
| name = "libc" | ||
| version = "0.2.154" | ||
| version = "0.2.171" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" | ||
| checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" | ||
| [[package]] | ||
| name = "lock_api" | ||
| version = "0.4.12" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" | ||
| dependencies = [ | ||
| "autocfg", | ||
| "scopeguard", | ||
| ] | ||
| [[package]] | ||
| name = "matrixmultiply" | ||
| version = "0.3.8" | ||
| version = "0.3.9" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" | ||
| checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" | ||
| dependencies = [ | ||
@@ -83,5 +67,5 @@ "autocfg", | ||
| name = "ndarray" | ||
| version = "0.15.6" | ||
| version = "0.16.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" | ||
| checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" | ||
| dependencies = [ | ||
@@ -92,2 +76,4 @@ "matrixmultiply", | ||
| "num-traits", | ||
| "portable-atomic", | ||
| "portable-atomic-util", | ||
| "rawpointer", | ||
@@ -98,5 +84,5 @@ ] | ||
| name = "num-complex" | ||
| version = "0.4.5" | ||
| version = "0.4.6" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" | ||
| checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" | ||
| dependencies = [ | ||
@@ -126,5 +112,5 @@ "num-traits", | ||
| name = "numpy" | ||
| version = "0.20.0" | ||
| version = "0.24.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "bef41cbb417ea83b30525259e30ccef6af39b31c240bda578889494c5392d331" | ||
| checksum = "a7cfbf3f0feededcaa4d289fe3079b03659e85c5b5a177f4ba6fb01ab4fb3e39" | ||
| dependencies = [ | ||
@@ -137,2 +123,3 @@ "libc", | ||
| "pyo3", | ||
| "pyo3-build-config", | ||
| "rustc-hash", | ||
@@ -143,40 +130,26 @@ ] | ||
| name = "once_cell" | ||
| version = "1.19.0" | ||
| version = "1.21.3" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" | ||
| checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" | ||
| [[package]] | ||
| name = "parking_lot" | ||
| version = "0.12.2" | ||
| name = "portable-atomic" | ||
| version = "1.11.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" | ||
| dependencies = [ | ||
| "lock_api", | ||
| "parking_lot_core", | ||
| ] | ||
| checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" | ||
| [[package]] | ||
| name = "parking_lot_core" | ||
| version = "0.9.10" | ||
| name = "portable-atomic-util" | ||
| version = "0.2.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" | ||
| checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" | ||
| dependencies = [ | ||
| "cfg-if", | ||
| "libc", | ||
| "redox_syscall", | ||
| "smallvec", | ||
| "windows-targets", | ||
| "portable-atomic", | ||
| ] | ||
| [[package]] | ||
| name = "portable-atomic" | ||
| version = "1.6.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" | ||
| [[package]] | ||
| name = "proc-macro2" | ||
| version = "1.0.81" | ||
| version = "1.0.94" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" | ||
| checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" | ||
| dependencies = [ | ||
@@ -188,5 +161,5 @@ "unicode-ident", | ||
| name = "pyo3" | ||
| version = "0.20.3" | ||
| version = "0.24.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" | ||
| checksum = "17da310086b068fbdcefbba30aeb3721d5bb9af8db4987d6735b2183ca567229" | ||
| dependencies = [ | ||
@@ -197,3 +170,3 @@ "cfg-if", | ||
| "memoffset", | ||
| "parking_lot", | ||
| "once_cell", | ||
| "portable-atomic", | ||
@@ -208,5 +181,5 @@ "pyo3-build-config", | ||
| name = "pyo3-build-config" | ||
| version = "0.20.3" | ||
| version = "0.24.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" | ||
| checksum = "e27165889bd793000a098bb966adc4300c312497ea25cf7a690a9f0ac5aa5fc1" | ||
| dependencies = [ | ||
@@ -219,5 +192,5 @@ "once_cell", | ||
| name = "pyo3-ffi" | ||
| version = "0.20.3" | ||
| version = "0.24.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" | ||
| checksum = "05280526e1dbf6b420062f3ef228b78c0c54ba94e157f5cb724a609d0f2faabc" | ||
| dependencies = [ | ||
@@ -230,5 +203,5 @@ "libc", | ||
| name = "pyo3-macros" | ||
| version = "0.20.3" | ||
| version = "0.24.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" | ||
| checksum = "5c3ce5686aa4d3f63359a5100c62a127c9f15e8398e5fdeb5deef1fed5cd5f44" | ||
| dependencies = [ | ||
@@ -243,5 +216,5 @@ "proc-macro2", | ||
| name = "pyo3-macros-backend" | ||
| version = "0.20.3" | ||
| version = "0.24.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" | ||
| checksum = "f4cf6faa0cbfb0ed08e89beb8103ae9724eb4750e3a78084ba4017cbe94f3855" | ||
| dependencies = [ | ||
@@ -257,5 +230,5 @@ "heck", | ||
| name = "quote" | ||
| version = "1.0.36" | ||
| version = "1.0.40" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" | ||
| checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" | ||
| dependencies = [ | ||
@@ -272,33 +245,12 @@ "proc-macro2", | ||
| [[package]] | ||
| name = "redox_syscall" | ||
| version = "0.5.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" | ||
| dependencies = [ | ||
| "bitflags", | ||
| ] | ||
| [[package]] | ||
| name = "rustc-hash" | ||
| version = "1.1.0" | ||
| version = "2.1.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" | ||
| checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" | ||
| [[package]] | ||
| name = "scopeguard" | ||
| version = "1.2.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" | ||
| [[package]] | ||
| name = "smallvec" | ||
| version = "1.13.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" | ||
| [[package]] | ||
| name = "syn" | ||
| version = "2.0.60" | ||
| version = "2.0.100" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" | ||
| checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" | ||
| dependencies = [ | ||
@@ -312,80 +264,16 @@ "proc-macro2", | ||
| name = "target-lexicon" | ||
| version = "0.12.14" | ||
| version = "0.13.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" | ||
| checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" | ||
| [[package]] | ||
| name = "unicode-ident" | ||
| version = "1.0.12" | ||
| version = "1.0.18" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" | ||
| checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" | ||
| [[package]] | ||
| name = "unindent" | ||
| version = "0.2.3" | ||
| version = "0.2.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" | ||
| [[package]] | ||
| name = "windows-targets" | ||
| version = "0.52.5" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" | ||
| dependencies = [ | ||
| "windows_aarch64_gnullvm", | ||
| "windows_aarch64_msvc", | ||
| "windows_i686_gnu", | ||
| "windows_i686_gnullvm", | ||
| "windows_i686_msvc", | ||
| "windows_x86_64_gnu", | ||
| "windows_x86_64_gnullvm", | ||
| "windows_x86_64_msvc", | ||
| ] | ||
| [[package]] | ||
| name = "windows_aarch64_gnullvm" | ||
| version = "0.52.5" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" | ||
| [[package]] | ||
| name = "windows_aarch64_msvc" | ||
| version = "0.52.5" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" | ||
| [[package]] | ||
| name = "windows_i686_gnu" | ||
| version = "0.52.5" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" | ||
| [[package]] | ||
| name = "windows_i686_gnullvm" | ||
| version = "0.52.5" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" | ||
| [[package]] | ||
| name = "windows_i686_msvc" | ||
| version = "0.52.5" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" | ||
| [[package]] | ||
| name = "windows_x86_64_gnu" | ||
| version = "0.52.5" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" | ||
| [[package]] | ||
| name = "windows_x86_64_gnullvm" | ||
| version = "0.52.5" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" | ||
| [[package]] | ||
| name = "windows_x86_64_msvc" | ||
| version = "0.52.5" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" | ||
| checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" |
+4
-4
| [package] | ||
| name = "fastpdb" | ||
| version = "1.3.1" | ||
| version = "1.3.2" | ||
| edition = "2018" | ||
| [dependencies] | ||
| numpy = "0.20" | ||
| ndarray = "0.15" | ||
| numpy = "0.24" | ||
| ndarray = "0.16" | ||
| [dependencies.pyo3] | ||
| version = "0.20" | ||
| version = "0.24" | ||
| features = ["extension-module"] | ||
@@ -13,0 +13,0 @@ |
+7
-2
@@ -1,4 +0,4 @@ | ||
| Metadata-Version: 2.1 | ||
| Metadata-Version: 2.4 | ||
| Name: fastpdb | ||
| Version: 1.3.1 | ||
| Version: 1.3.2 | ||
| Classifier: Development Status :: 5 - Production/Stable | ||
@@ -14,2 +14,7 @@ Classifier: Intended Audience :: Science/Research | ||
| Requires-Dist: biotite >=0.39 | ||
| Requires-Dist: pytest ; extra == 'test' | ||
| Requires-Dist: pytest-codspeed ; extra == 'test' | ||
| Requires-Dist: matplotlib ; extra == 'plot' | ||
| Provides-Extra: test | ||
| Provides-Extra: plot | ||
| License-File: LICENSE | ||
@@ -16,0 +21,0 @@ Summary: A high performance drop-in replacement for Biotite's PDBFile. |
+14
-4
| [project] | ||
| name = "fastpdb" | ||
| version = "1.3.1" | ||
| version = "1.3.2" | ||
| description = "A high performance drop-in replacement for Biotite's PDBFile." | ||
@@ -28,5 +28,15 @@ readme = "README.rst" | ||
| dependencies = [ | ||
| "biotite >= 0.39" | ||
| "biotite >= 0.39", | ||
| ] | ||
| [project.optional-dependencies] | ||
| # development dependency groups | ||
| test = [ | ||
| "pytest", | ||
| "pytest-codspeed", | ||
| ] | ||
| plot = [ | ||
| "matplotlib", | ||
| ] | ||
| [project.urls] | ||
@@ -42,5 +52,5 @@ homepage = "https://github.com/biotite-dev/fastpdb" | ||
| requires = [ | ||
| "maturin==1.4", | ||
| "oldest-supported-numpy" | ||
| "maturin >=1.0,<2.0", | ||
| "numpy >=1.26,<1.27" | ||
| ] | ||
| build-backend = "maturin" |
| __name__ = "fastpdb" | ||
| __author__ = "Patrick Kunzmann" | ||
| __all__ = ["PDBFile"] | ||
| __version__ = "1.3.1" | ||
| __version__ = "1.3.2" | ||
@@ -140,2 +140,14 @@ import os | ||
| # Replace empty strings for elements with guessed types | ||
| # This is used e.g. for PDB files created by Gromacs | ||
| empty_element_mask = element == "" | ||
| if empty_element_mask.any(): | ||
| warnings.warn( | ||
| f"{np.count_nonzero(empty_element_mask)} elements " | ||
| "were guessed from atom name" | ||
| ) | ||
| element[empty_element_mask] = struc.infer_elements( | ||
| atom_name[empty_element_mask] | ||
| ) | ||
| if coord.ndim == 3: | ||
@@ -142,0 +154,0 @@ atoms = struc.AtomArrayStack(coord.shape[0], coord.shape[1]) |
+215
-205
@@ -8,6 +8,12 @@ //! Low-level PDB file parsing and writing. | ||
| use std::cmp::Ordering; | ||
| use ndarray::{Array, Ix1, Ix2, Ix3}; | ||
| use pyo3::prelude::*; | ||
| use pyo3::exceptions; | ||
| use numpy::PyArray; | ||
| use numpy::{ | ||
| PyArray1, PyArray2, PyArray3, | ||
| PyReadonlyArray1, PyReadonlyArray2, PyReadonlyArray3, | ||
| IntoPyArray | ||
| }; | ||
| use numpy::ndarray::{ | ||
| Array, Array1, Array2, Array3, ArrayView2 | ||
| }; | ||
@@ -27,4 +33,4 @@ | ||
| enum CoordArray { | ||
| Single(Array<f32, Ix2>), | ||
| Multi(Array<f32, Ix3>) | ||
| Single(Array2<f32>), | ||
| Multi(Array3<f32>) | ||
| } | ||
@@ -86,8 +92,6 @@ | ||
| #[getter] | ||
| fn model_start_i(&self) -> PyResult<Py<PyArray<i64, Ix1>>> { | ||
| Python::with_gil(|py| { | ||
| Ok(PyArray::from_iter( | ||
| py, self.model_start_i.iter().map(|x| *x as i64) | ||
| ).to_owned()) | ||
| }) | ||
| fn model_start_i<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray1<i64>>> { | ||
| Ok(PyArray1::from_iter_bound( | ||
| py, self.model_start_i.iter().map(|x| *x as i64) | ||
| )) | ||
| } | ||
@@ -97,8 +101,6 @@ | ||
| #[getter] | ||
| fn atom_line_i(&self) -> PyResult<Py<PyArray<i64, Ix1>>> { | ||
| Python::with_gil(|py| { | ||
| Ok(PyArray::from_iter( | ||
| py, self.atom_line_i.iter().map(|x| *x as i64) | ||
| ).to_owned()) | ||
| }) | ||
| fn atom_line_i<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray1<i64>>> { | ||
| Ok(PyArray1::from_iter_bound( | ||
| py, self.atom_line_i.iter().map(|x| *x as i64) | ||
| ).to_owned()) | ||
| } | ||
@@ -157,10 +159,12 @@ | ||
| /// `model` is the 1-based number of the requested model. | ||
| fn parse_coord_single_model(&self, model: isize) -> PyResult<Py<PyArray<f32, Ix2>>> { | ||
| fn parse_coord_single_model<'py>( | ||
| &self, | ||
| py: Python<'py>, | ||
| model: isize | ||
| ) -> PyResult<Bound<'py, PyArray2<f32>>> { | ||
| let array = self.parse_coord(Some(model))?; | ||
| Python::with_gil(|py| { | ||
| match array { | ||
| CoordArray::Single(array) => Ok(PyArray::from_array(py, &array).to_owned()), | ||
| CoordArray::Multi(_) => panic!("No multi-model coordinates should be returned"), | ||
| } | ||
| }) | ||
| match array { | ||
| CoordArray::Single(array) => Ok(array.into_pyarray_bound(py)), | ||
| CoordArray::Multi(_) => panic!("No multi-model coordinates should be returned"), | ||
| } | ||
| } | ||
@@ -171,10 +175,11 @@ | ||
| /// A `PyErr` is returned if the number of atoms per model differ from each other. | ||
| fn parse_coord_multi_model(&self) -> PyResult<Py<PyArray<f32, Ix3>>> { | ||
| fn parse_coord_multi_model<'py>( | ||
| &self, | ||
| py: Python<'py> | ||
| ) -> PyResult<Bound<'py, PyArray3<f32>>> { | ||
| let array = self.parse_coord(None)?; | ||
| Python::with_gil(|py| { | ||
| match array { | ||
| CoordArray::Single(_) => panic!("No single-model coordinates should be returned"), | ||
| CoordArray::Multi(array) => Ok(PyArray::from_array(py, &array).to_owned()), | ||
| } | ||
| }) | ||
| match array { | ||
| CoordArray::Single(_) => panic!("No single-model coordinates should be returned"), | ||
| CoordArray::Multi(array) => Ok(array.into_pyarray_bound(py)) | ||
| } | ||
| } | ||
@@ -202,31 +207,36 @@ | ||
| /// - `charge` | ||
| fn parse_annotations(&self, | ||
| model: isize, | ||
| include_atom_id: bool, | ||
| include_b_factor: bool, | ||
| include_occupancy: bool, | ||
| include_charge: bool) -> PyResult<(Py<PyArray<u32, Ix2>>, | ||
| Py<PyArray<i64, Ix1>>, | ||
| Py<PyArray<u32, Ix2>>, | ||
| Py<PyArray<u32, Ix2>>, | ||
| Py<PyArray<bool, Ix1>>, | ||
| Py<PyArray<u32, Ix2>>, | ||
| Py<PyArray<u32, Ix2>>, | ||
| Py<PyArray<u32, Ix2>>, | ||
| Option<Py<PyArray<i64, Ix1>>>, | ||
| Option<Py<PyArray<f64, Ix1>>>, | ||
| Option<Py<PyArray<f64, Ix1>>>, | ||
| Option<Py<PyArray<i64, Ix1>>>)> { | ||
| fn parse_annotations<'py>( | ||
| &self, | ||
| py: Python<'py>, | ||
| model: isize, | ||
| include_atom_id: bool, | ||
| include_b_factor: bool, | ||
| include_occupancy: bool, | ||
| include_charge: bool | ||
| ) -> PyResult<( | ||
| Bound<'py, PyArray2<u32>>, | ||
| Bound<'py, PyArray1<i64>>, | ||
| Bound<'py, PyArray2<u32>>, | ||
| Bound<'py, PyArray2<u32>>, | ||
| Bound<'py, PyArray1<bool>>, | ||
| Bound<'py, PyArray2<u32>>, | ||
| Bound<'py, PyArray2<u32>>, | ||
| Bound<'py, PyArray2<u32>>, | ||
| Option<Bound<'py, PyArray1<i64>>>, | ||
| Option<Bound<'py, PyArray1<f64>>>, | ||
| Option<Bound<'py, PyArray1<f64>>>, | ||
| Option<Bound<'py, PyArray1<i64>>> | ||
| )> { | ||
| let atom_line_i: Vec<usize> = self.get_atom_indices(model)?; | ||
| let mut chain_id: Array<u32, Ix2> = Array::zeros((atom_line_i.len(), 4)); | ||
| let mut res_id: Array<i64, Ix1> = Array::zeros(atom_line_i.len()); | ||
| let mut ins_code: Array<u32, Ix2> = Array::zeros((atom_line_i.len(), 1)); | ||
| let mut res_name: Array<u32, Ix2> = Array::zeros((atom_line_i.len(), 5)); | ||
| let mut hetero: Array<bool, Ix1> = Array::default(atom_line_i.len()); | ||
| let mut atom_name: Array<u32, Ix2> = Array::zeros((atom_line_i.len(), 6)); | ||
| let mut element: Array<u32, Ix2> = Array::zeros((atom_line_i.len(), 2)); | ||
| let mut altloc_id: Array<u32, Ix2> = Array::zeros((atom_line_i.len(), 1)); | ||
| let mut chain_id: Array2<u32> = Array::zeros((atom_line_i.len(), 4)); | ||
| let mut res_id: Array1<i64> = Array::zeros(atom_line_i.len()); | ||
| let mut ins_code: Array2<u32> = Array::zeros((atom_line_i.len(), 1)); | ||
| let mut res_name: Array2<u32> = Array::zeros((atom_line_i.len(), 5)); | ||
| let mut hetero: Array1<bool> = Array::default(atom_line_i.len()); | ||
| let mut atom_name: Array2<u32> = Array::zeros((atom_line_i.len(), 6)); | ||
| let mut element: Array2<u32> = Array::zeros((atom_line_i.len(), 2)); | ||
| let mut altloc_id: Array2<u32> = Array::zeros((atom_line_i.len(), 1)); | ||
| let mut atom_id: Array<i64, Ix1>; | ||
| let mut atom_id: Array1<i64>; | ||
| if include_atom_id { | ||
@@ -239,3 +249,3 @@ atom_id = Array::zeros(atom_line_i.len()); | ||
| } | ||
| let mut b_factor: Array<f64, Ix1>; | ||
| let mut b_factor: Array1<f64>; | ||
| if include_b_factor { | ||
@@ -247,3 +257,3 @@ b_factor = Array::zeros(atom_line_i.len()); | ||
| } | ||
| let mut occupancy: Array<f64, Ix1>; | ||
| let mut occupancy: Array1<f64>; | ||
| if include_occupancy { | ||
@@ -255,3 +265,3 @@ occupancy = Array::zeros(atom_line_i.len()); | ||
| } | ||
| let mut charge: Array<i64, Ix1>; | ||
| let mut charge: Array1<i64>; | ||
| if include_charge { | ||
@@ -307,18 +317,16 @@ charge = Array::zeros(atom_line_i.len()); | ||
| Python::with_gil(|py| { | ||
| Ok(( | ||
| PyArray::from_array(py, &chain_id ).to_owned(), | ||
| PyArray::from_array(py, &res_id ).to_owned(), | ||
| PyArray::from_array(py, &ins_code ).to_owned(), | ||
| PyArray::from_array(py, &res_name ).to_owned(), | ||
| PyArray::from_array(py, &hetero ).to_owned(), | ||
| PyArray::from_array(py, &atom_name).to_owned(), | ||
| PyArray::from_array(py, &element ).to_owned(), | ||
| PyArray::from_array(py, &altloc_id).to_owned(), | ||
| if include_atom_id { Some(PyArray::from_array(py, &atom_id).to_owned()) } else {None}, | ||
| if include_b_factor { Some(PyArray::from_array(py, &b_factor).to_owned()) } else {None}, | ||
| if include_occupancy { Some(PyArray::from_array(py, &occupancy).to_owned()) } else {None}, | ||
| if include_charge { Some(PyArray::from_array(py, &charge).to_owned()) } else {None}, | ||
| )) | ||
| }) | ||
| Ok(( | ||
| chain_id.into_pyarray_bound(py), | ||
| res_id.into_pyarray_bound(py), | ||
| ins_code.into_pyarray_bound(py), | ||
| res_name.into_pyarray_bound(py), | ||
| hetero.into_pyarray_bound(py), | ||
| atom_name.into_pyarray_bound(py), | ||
| element.into_pyarray_bound(py), | ||
| altloc_id.into_pyarray_bound(py), | ||
| if include_atom_id { Some(atom_id.into_pyarray_bound(py)) } else {None}, | ||
| if include_b_factor { Some(b_factor.into_pyarray_bound(py)) } else {None}, | ||
| if include_occupancy { Some(occupancy.into_pyarray_bound(py)) } else {None}, | ||
| if include_charge { Some(charge.into_pyarray_bound(py)) } else {None}, | ||
| )) | ||
| } | ||
@@ -332,10 +340,12 @@ | ||
| /// to atom indices. | ||
| fn parse_bonds(&self, atom_id: Py<PyArray<i64, Ix1>>) -> PyResult<Py<PyArray<u32, Ix2>>> { | ||
| fn parse_bonds<'py>( | ||
| &self, | ||
| py: Python<'py>, | ||
| atom_id: PyReadonlyArray1<'py, i64> | ||
| ) -> PyResult<Bound<'py, PyArray2<u32>>> { | ||
| // Mapping from atom ids to indices in an AtomArray | ||
| let mut atom_id_to_index: HashMap<i64, u32> = HashMap::new(); | ||
| Python::with_gil(|py| { | ||
| for (i, id) in atom_id.as_ref(py).to_owned_array().iter().enumerate() { | ||
| atom_id_to_index.insert(*id, i as u32); | ||
| } | ||
| }); | ||
| for (i, id) in atom_id.as_array().iter().enumerate() { | ||
| atom_id_to_index.insert(*id, i as u32); | ||
| } | ||
@@ -363,3 +373,3 @@ // Cannot preemptively determine number of bonds | ||
| let mut bond_array: Array<u32, Ix2> = Array::zeros((bonds.len(), 2)); | ||
| let mut bond_array: Array2<u32> = Array::zeros((bonds.len(), 2)); | ||
| for (i, (center_id, bonded_id)) in bonds.iter().enumerate() { | ||
@@ -369,5 +379,3 @@ bond_array[[i, 0]] = *center_id; | ||
| } | ||
| Python::with_gil(|py| { | ||
| Ok(PyArray::from_array(py, &bond_array).to_owned()) | ||
| }) | ||
| Ok(bond_array.into_pyarray_bound(py)) | ||
| } | ||
@@ -379,5 +387,7 @@ | ||
| /// Write the `CRYST1` record to this [`PDBFile`] based on the given unit cell parameters. | ||
| fn write_box(&mut self, | ||
| len_a: f32, len_b: f32, len_c: f32, | ||
| alpha: f32, beta: f32, gamma: f32) { | ||
| fn write_box( | ||
| &mut self, | ||
| len_a: f32, len_b: f32, len_c: f32, | ||
| alpha: f32, beta: f32, gamma: f32 | ||
| ) { | ||
| self.lines.push( | ||
@@ -393,91 +403,91 @@ format!( | ||
| /// Write models to this [`PDBFile`] based on the given coordinates and annotation arrays. | ||
| fn write_models(&mut self, | ||
| coord: Py<PyArray<f32, Ix3>>, | ||
| chain_id: Py<PyArray<u32, Ix2>>, | ||
| res_id: Py<PyArray<i64, Ix1>>, | ||
| ins_code: Py<PyArray<u32, Ix2>>, | ||
| res_name: Py<PyArray<u32, Ix2>>, | ||
| hetero: Py<PyArray<bool, Ix1>>, | ||
| atom_name: Py<PyArray<u32, Ix2>>, | ||
| element: Py<PyArray<u32, Ix2>>, | ||
| atom_id: Option<Py<PyArray<i64, Ix1>>>, | ||
| b_factor: Option<Py<PyArray<f64, Ix1>>>, | ||
| occupancy: Option<Py<PyArray<f64, Ix1>>>, | ||
| charge: Option<Py<PyArray<i64, Ix1>>>) -> PyResult<()> { | ||
| Python::with_gil(|py| { | ||
| let coord = coord.as_ref(py).to_owned_array(); | ||
| let chain_id = chain_id.as_ref(py).to_owned_array(); | ||
| let res_id = res_id.as_ref(py).to_owned_array(); | ||
| let ins_code = ins_code.as_ref(py).to_owned_array(); | ||
| let res_name = res_name.as_ref(py).to_owned_array(); | ||
| let hetero = hetero.as_ref(py).to_owned_array(); | ||
| let atom_name = atom_name.as_ref(py).to_owned_array(); | ||
| let element = element.as_ref(py).to_owned_array(); | ||
| let atom_id = atom_id.map(|arr| arr.as_ref(py).to_owned_array()); | ||
| let b_factor = b_factor.map(|arr| arr.as_ref(py).to_owned_array()); | ||
| let occupancy = occupancy.map(|arr| arr.as_ref(py).to_owned_array()); | ||
| let charge = charge.map(|arr| arr.as_ref(py).to_owned_array()); | ||
| fn write_models<'py>( | ||
| &mut self, | ||
| coord: PyReadonlyArray3<'py, f32>, | ||
| chain_id: PyReadonlyArray2<'py, u32>, | ||
| res_id: PyReadonlyArray1<'py, i64>, | ||
| ins_code: PyReadonlyArray2<'py, u32>, | ||
| res_name: PyReadonlyArray2<'py, u32>, | ||
| hetero: PyReadonlyArray1<'py, bool>, | ||
| atom_name: PyReadonlyArray2<'py, u32>, | ||
| element: PyReadonlyArray2<'py, u32>, | ||
| atom_id: Option<PyReadonlyArray1<'py, i64>>, | ||
| b_factor: Option<PyReadonlyArray1<'py, f64>>, | ||
| occupancy: Option<PyReadonlyArray1<'py, f64>>, | ||
| charge: Option<PyReadonlyArray1<'py, i64>> | ||
| ) -> PyResult<()> { | ||
| let coord = coord.as_array(); | ||
| let chain_id = chain_id.as_array(); | ||
| let res_id = res_id.as_array(); | ||
| let ins_code = ins_code.as_array(); | ||
| let res_name = res_name.as_array(); | ||
| let hetero = hetero.as_array(); | ||
| let atom_name = atom_name.as_array(); | ||
| let element = element.as_array(); | ||
| let atom_id = atom_id.as_ref().map(|arr| arr.as_array()); | ||
| let b_factor = b_factor.as_ref().map(|arr| arr.as_array()); | ||
| let occupancy = occupancy.as_ref().map(|arr| arr.as_array()); | ||
| let charge = charge.as_ref().map(|arr| arr.as_array()); | ||
| let is_multi_model = coord.shape()[0] > 1; | ||
| let is_multi_model = coord.shape()[0] > 1; | ||
| // These will contain the ATOM records for each atom | ||
| // These are reused in every model by adding the coordinates to the string | ||
| // This procedure aims to increase the performance is repetitive formatting is omitted | ||
| let mut prefix: Vec<String> = Vec::new(); | ||
| let mut suffix: Vec<String> = Vec::new(); | ||
| // These will contain the ATOM records for each atom | ||
| // These are reused in every model by adding the coordinates to the string | ||
| // This procedure aims to increase the performance is repetitive formatting is omitted | ||
| let mut prefix: Vec<String> = Vec::new(); | ||
| let mut suffix: Vec<String> = Vec::new(); | ||
| for i in 0..coord.shape()[1] { | ||
| let element_i = parse_string_from_array(&element, i)?; | ||
| let atom_name_i = parse_string_from_array(&atom_name, i)?; | ||
| for i in 0..coord.shape()[1] { | ||
| let element_i = parse_string_from_array(&element, i)?; | ||
| let atom_name_i = parse_string_from_array(&atom_name, i)?; | ||
| prefix.push(format!( | ||
| "{:6}{:>5} {:4} {:>3} {:1}{:>4}{:1} ", | ||
| if hetero[i] { "HETATM" } else { "ATOM" }, | ||
| atom_id.as_ref().map_or((i+1) as i64, |arr| truncate_id(arr[i], 99999)), | ||
| if element_i.len() == 1 && atom_name_i.len() < 4 { | ||
| format!(" {}", atom_name_i) | ||
| } else { | ||
| atom_name_i | ||
| }, | ||
| parse_string_from_array(&res_name, i)?, | ||
| parse_string_from_array(&chain_id, i)?, | ||
| truncate_id(res_id[i], 9999), | ||
| parse_string_from_array(&ins_code, i)?, | ||
| )); | ||
| prefix.push(format!( | ||
| "{:6}{:>5} {:4} {:>3} {:1}{:>4}{:1} ", | ||
| if hetero[i] { "HETATM" } else { "ATOM" }, | ||
| atom_id.as_ref().map_or((i+1) as i64, |arr| truncate_id(arr[i], 99999)), | ||
| if element_i.len() == 1 && atom_name_i.len() < 4 { | ||
| format!(" {}", atom_name_i) | ||
| } else { | ||
| atom_name_i | ||
| }, | ||
| parse_string_from_array(&res_name, i)?, | ||
| parse_string_from_array(&chain_id, i)?, | ||
| truncate_id(res_id[i], 9999), | ||
| parse_string_from_array(&ins_code, i)?, | ||
| )); | ||
| suffix.push(format!( | ||
| "{:>6.2}{:>6.2} {:>2}{}", | ||
| occupancy.as_ref().map_or(1f64, |arr| arr[i]), | ||
| b_factor.as_ref().map_or(0f64, |arr| arr[i]), | ||
| element_i, | ||
| charge.as_ref().map_or(String::from(" "), |arr| { | ||
| let c = arr[i]; | ||
| match c.cmp(&0) { | ||
| Ordering::Greater => format!("{:1}+", c), | ||
| Ordering::Less => format!("{:1}-", -c), | ||
| Ordering::Equal => String::from(" ") | ||
| } | ||
| }), | ||
| )); | ||
| } | ||
| suffix.push(format!( | ||
| "{:>6.2}{:>6.2} {:>2}{}", | ||
| occupancy.as_ref().map_or(1f64, |arr| arr[i]), | ||
| b_factor.as_ref().map_or(0f64, |arr| arr[i]), | ||
| element_i, | ||
| charge.as_ref().map_or(String::from(" "), |arr| { | ||
| let c = arr[i]; | ||
| match c.cmp(&0) { | ||
| Ordering::Greater => format!("{:1}+", c), | ||
| Ordering::Less => format!("{:1}-", -c), | ||
| Ordering::Equal => String::from(" ") | ||
| } | ||
| }), | ||
| )); | ||
| } | ||
| for model_i in 0..coord.shape()[0] { | ||
| if is_multi_model { | ||
| self.lines.push(format!("MODEL {:>8}", model_i+1)); | ||
| } | ||
| for atom_i in 0..coord.shape()[1] { | ||
| let coord_string = format!( | ||
| "{:>8.3}{:>8.3}{:>8.3}", | ||
| coord[[model_i, atom_i, 0]], | ||
| coord[[model_i, atom_i, 1]], | ||
| coord[[model_i, atom_i, 2]], | ||
| ); | ||
| self.lines.push(prefix[atom_i].clone() + &coord_string + &suffix[atom_i]); | ||
| } | ||
| if is_multi_model { | ||
| self.lines.push(String::from("ENDMDL")); | ||
| } | ||
| for model_i in 0..coord.shape()[0] { | ||
| if is_multi_model { | ||
| self.lines.push(format!("MODEL {:>8}", model_i+1)); | ||
| } | ||
| Ok(()) | ||
| }) | ||
| for atom_i in 0..coord.shape()[1] { | ||
| let coord_string = format!( | ||
| "{:>8.3}{:>8.3}{:>8.3}", | ||
| coord[[model_i, atom_i, 0]], | ||
| coord[[model_i, atom_i, 1]], | ||
| coord[[model_i, atom_i, 2]], | ||
| ); | ||
| self.lines.push(prefix[atom_i].clone() + &coord_string + &suffix[atom_i]); | ||
| } | ||
| if is_multi_model { | ||
| self.lines.push(String::from("ENDMDL")); | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
@@ -490,38 +500,38 @@ | ||
| /// to atom indices. | ||
| fn write_bonds(&mut self, | ||
| bonds: Py<PyArray<i32, Ix2>>, | ||
| atom_id: Py<PyArray<i64, Ix1>>) -> PyResult<()> { | ||
| Python::with_gil(|py| { | ||
| let bonds = bonds.as_ref(py).to_owned_array(); | ||
| let atom_id = atom_id.as_ref(py).to_owned_array(); | ||
| fn write_bonds<'py>( | ||
| &mut self, | ||
| bonds: PyReadonlyArray2<'py, i32>, | ||
| atom_id: PyReadonlyArray1<'py, i64> | ||
| ) -> PyResult<()> { | ||
| let bonds = bonds.as_array(); | ||
| let atom_id = atom_id.as_array(); | ||
| for (center_i, bonded_indices) in bonds.outer_iter().enumerate() { | ||
| let mut n_added: usize = 0; | ||
| let mut line: String = String::new(); | ||
| for bonded_i in bonded_indices.iter() { | ||
| if *bonded_i == -1 { | ||
| // Reached padding values | ||
| break; | ||
| } | ||
| if n_added == 0 { | ||
| // Add new record | ||
| line.push_str(&format!("CONECT{:>5}", atom_id[center_i])); | ||
| } | ||
| line.push_str(&format!("{:>5}", atom_id[*bonded_i as usize])); | ||
| n_added += 1; | ||
| if n_added == 4 { | ||
| // Only a maximum of 4 bond partners can be put | ||
| // into a single line | ||
| // If there are more, use an extra record | ||
| n_added = 0; | ||
| self.lines.push(line); | ||
| line = String::new(); | ||
| } | ||
| for (center_i, bonded_indices) in bonds.outer_iter().enumerate() { | ||
| let mut n_added: usize = 0; | ||
| let mut line: String = String::new(); | ||
| for bonded_i in bonded_indices.iter() { | ||
| if *bonded_i == -1 { | ||
| // Reached padding values | ||
| break; | ||
| } | ||
| if n_added > 0 { | ||
| if n_added == 0 { | ||
| // Add new record | ||
| line.push_str(&format!("CONECT{:>5}", atom_id[center_i])); | ||
| } | ||
| line.push_str(&format!("{:>5}", atom_id[*bonded_i as usize])); | ||
| n_added += 1; | ||
| if n_added == 4 { | ||
| // Only a maximum of 4 bond partners can be put | ||
| // into a single line | ||
| // If there are more, use an extra record | ||
| n_added = 0; | ||
| self.lines.push(line); | ||
| line = String::new(); | ||
| } | ||
| } | ||
| Ok(()) | ||
| }) | ||
| if n_added > 0 { | ||
| self.lines.push(line); | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
@@ -552,3 +562,3 @@ | ||
| /// Get a *NumPy* array containing coordinates for a single model (2D) or | ||
| /// Get an `ndarray` containing coordinates for a single model (2D) or | ||
| /// multiple models (3D) in the file. | ||
@@ -668,3 +678,3 @@ /// The number of returned dimensions is based on whether a `model` is | ||
| #[inline(always)] | ||
| fn write_string_to_array(array: &mut Array<u32, Ix2>, index: usize, string: &str) { | ||
| fn write_string_to_array(array: &mut Array2<u32>, index: usize, string: &str) { | ||
| for (i, char) in string.chars().enumerate() { | ||
@@ -680,3 +690,3 @@ array[[index, i]] = char as u32 | ||
| #[inline(always)] | ||
| fn parse_string_from_array(array: &Array<u32, Ix2>, index: usize) -> PyResult<String> { | ||
| fn parse_string_from_array(array: &ArrayView2<u32>, index: usize) -> PyResult<String> { | ||
| let mut out_string: String = String::new(); | ||
@@ -745,5 +755,5 @@ for i in 0..array.shape()[1] { | ||
| #[pymodule] | ||
| fn fastpdb(_py: Python, m: &PyModule) -> PyResult<()> { | ||
| fn fastpdb(m: &Bound<'_, PyModule>) -> PyResult<()> { | ||
| m.add_class::<PDBFile>()?; | ||
| Ok(()) | ||
| } |
+41
-25
@@ -11,5 +11,4 @@ # This source code is part of the Biotite package and is distributed | ||
| import itertools | ||
| import glob | ||
| from io import StringIO | ||
| from os.path import join, dirname, realpath | ||
| from pathlib import Path | ||
| import pytest | ||
@@ -21,11 +20,11 @@ import biotite | ||
| DATA_PATH = join(dirname(realpath(__file__)), "data") | ||
| TEST_STRUCTURES = glob.glob(join(DATA_PATH, "*.pdb")) | ||
| DATA_PATH = Path(__file__).parent / "data" | ||
| TEST_STRUCTURES = list(DATA_PATH.glob("*.pdb")) | ||
| def test_get_remark(): | ||
| ref_file = pdb.PDBFile.read(join(DATA_PATH, "1aki.pdb")) | ||
| test_file = fastpdb.PDBFile.read(join(DATA_PATH, "1aki.pdb")) | ||
| ref_file = pdb.PDBFile.read(DATA_PATH / "1aki.pdb") | ||
| test_file = fastpdb.PDBFile.read(DATA_PATH / "1aki.pdb") | ||
| for remark in np.arange(0, 1000): | ||
@@ -40,6 +39,6 @@ assert test_file.get_remark(remark) == ref_file.get_remark(remark) | ||
| ref_file = pdb.PDBFile.read(path) | ||
| test_file = fastpdb.PDBFile.read(path) | ||
| assert ref_file.get_model_count() == test_file.get_model_count() | ||
@@ -66,3 +65,3 @@ | ||
| raise | ||
| test_file = fastpdb.PDBFile.read(path) | ||
@@ -90,4 +89,4 @@ test_coord = test_file.get_coord(model) | ||
| extra_fields = None | ||
| ref_file = pdb.PDBFile.read(path) | ||
@@ -106,3 +105,2 @@ try: | ||
| test_file = fastpdb.PDBFile.read(path) | ||
@@ -113,3 +111,3 @@ test_atoms = test_file.get_structure( | ||
| if ref_atoms.box is not None: | ||
@@ -119,5 +117,5 @@ assert np.allclose(test_atoms.box, ref_atoms.box) | ||
| assert test_atoms.box is None | ||
| assert test_atoms.bonds == ref_atoms.bonds | ||
| for category in ref_atoms.get_annotation_categories(): | ||
@@ -130,3 +128,3 @@ if np.issubdtype(ref_atoms.get_annotation(category).dtype, float): | ||
| == ref_atoms.get_annotation(category).tolist() | ||
| assert np.allclose(test_atoms.coord, ref_atoms.coord) | ||
@@ -150,4 +148,3 @@ | ||
| extra_fields = None | ||
| input_file = pdb.PDBFile.read(path) | ||
@@ -166,3 +163,2 @@ try: | ||
| ref_file = pdb.PDBFile() | ||
@@ -178,3 +174,2 @@ ref_file.set_structure(atoms) | ||
| assert test_file_content.getvalue() == ref_file_content.getvalue() | ||
@@ -189,6 +184,27 @@ | ||
| """ | ||
| ref_file = pdb.PDBFile.read(join(DATA_PATH, "1aki.pdb")) | ||
| test_file = fastpdb.PDBFile.read(join(DATA_PATH, "1aki.pdb")) | ||
| ref_file = pdb.PDBFile.read(DATA_PATH / "1aki.pdb") | ||
| assert test_file.get_assembly() == ref_file.get_assembly() | ||
| test_file = fastpdb.PDBFile.read(DATA_PATH / "1aki.pdb") | ||
| assert test_file.get_assembly() == ref_file.get_assembly() | ||
| @pytest.mark.filterwarnings("ignore") | ||
| def test_inferred_elements(tmp_path): | ||
| # Read valid pdb file | ||
| pdb_file = fastpdb.PDBFile.read(DATA_PATH / "1l2y.pdb") | ||
| atoms = pdb_file.get_structure() | ||
| # Remove all elements | ||
| atoms_wo_elements = atoms.copy() | ||
| atoms_wo_elements.element[:] = '' | ||
| # Save stack without elements to file | ||
| temp = tmp_path / "tmp.pdb" | ||
| tmp_pdb_file = pdb.PDBFile() | ||
| tmp_pdb_file.set_structure(atoms_wo_elements) | ||
| tmp_pdb_file.write(temp) | ||
| # Read new stack from file with guessed elements | ||
| guessed_pdb_file = fastpdb.PDBFile.read(temp) | ||
| atoms_guessed_elements = guessed_pdb_file.get_structure() | ||
| assert atoms_guessed_elements.element.tolist() == atoms.element.tolist() |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
33
13.79%594
12.29%13238757
-0.01%