Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoSign in
Socket

fastpdb

Package Overview
Dependencies
Maintainers
2
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fastpdb - pypi Package Compare versions

Comparing version
1.3.1
to
1.3.2
benchmarks/__init__.py
+13
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)
+54
-30

@@ -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 }}

@@ -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"
[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 @@

@@ -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.

[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(())
}

@@ -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()