torch-structure-manipulation 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- torch_structure_manipulation-0.6.0/.gitignore +115 -0
- torch_structure_manipulation-0.6.0/LICENSE +29 -0
- torch_structure_manipulation-0.6.0/PKG-INFO +64 -0
- torch_structure_manipulation-0.6.0/README.md +38 -0
- torch_structure_manipulation-0.6.0/pyproject.toml +95 -0
- torch_structure_manipulation-0.6.0/src/torch_structure_manipulation/__init__.py +54 -0
- torch_structure_manipulation-0.6.0/src/torch_structure_manipulation/atomic_structure.py +228 -0
- torch_structure_manipulation-0.6.0/src/torch_structure_manipulation/bonding.py +260 -0
- torch_structure_manipulation-0.6.0/src/torch_structure_manipulation/bonding_data.json +679 -0
- torch_structure_manipulation-0.6.0/src/torch_structure_manipulation/py.typed +1 -0
- torch_structure_manipulation-0.6.0/src/torch_structure_manipulation/structure_transforms/__init__.py +45 -0
- torch_structure_manipulation-0.6.0/src/torch_structure_manipulation/structure_transforms/center_molecule.py +180 -0
- torch_structure_manipulation-0.6.0/src/torch_structure_manipulation/structure_transforms/rotate_molecule.py +118 -0
- torch_structure_manipulation-0.6.0/src/torch_structure_manipulation/structure_transforms/select_atoms.py +172 -0
- torch_structure_manipulation-0.6.0/src/torch_structure_manipulation/structure_transforms/translate_molecule.py +79 -0
- torch_structure_manipulation-0.6.0/src/torch_structure_manipulation/structure_transforms/utils.py +110 -0
- torch_structure_manipulation-0.6.0/tests/test_atomic_structure.py +151 -0
- torch_structure_manipulation-0.6.0/tests/test_bonding.py +98 -0
- torch_structure_manipulation-0.6.0/tests/test_structure_transforms.py +436 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
env/
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
|
|
28
|
+
.DS_Store
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
*.manifest
|
|
32
|
+
*.spec
|
|
33
|
+
|
|
34
|
+
# Installer logs
|
|
35
|
+
pip-log.txt
|
|
36
|
+
pip-delete-this-directory.txt
|
|
37
|
+
|
|
38
|
+
# Unit test / coverage reports
|
|
39
|
+
htmlcov/
|
|
40
|
+
.tox/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
.cache
|
|
44
|
+
nosetests.xml
|
|
45
|
+
coverage.xml
|
|
46
|
+
*.cover
|
|
47
|
+
.hypothesis/
|
|
48
|
+
.pytest_cache/
|
|
49
|
+
|
|
50
|
+
# Files downloaded for unit tests
|
|
51
|
+
**/tests/tmp/
|
|
52
|
+
|
|
53
|
+
# Translations
|
|
54
|
+
*.mo
|
|
55
|
+
*.pot
|
|
56
|
+
|
|
57
|
+
# Django stuff:
|
|
58
|
+
*.log
|
|
59
|
+
local_settings.py
|
|
60
|
+
|
|
61
|
+
# Flask stuff:
|
|
62
|
+
instance/
|
|
63
|
+
.webassets-cache
|
|
64
|
+
|
|
65
|
+
# Scrapy stuff:
|
|
66
|
+
.scrapy
|
|
67
|
+
|
|
68
|
+
# Sphinx documentation
|
|
69
|
+
docs/_build/
|
|
70
|
+
|
|
71
|
+
# PyBuilder
|
|
72
|
+
target/
|
|
73
|
+
|
|
74
|
+
# Jupyter Notebook
|
|
75
|
+
.ipynb_checkpoints
|
|
76
|
+
|
|
77
|
+
# dotenv
|
|
78
|
+
.env
|
|
79
|
+
|
|
80
|
+
# virtualenv
|
|
81
|
+
.venv
|
|
82
|
+
venv/
|
|
83
|
+
ENV/
|
|
84
|
+
|
|
85
|
+
# Spyder project settings
|
|
86
|
+
.spyderproject
|
|
87
|
+
.spyproject
|
|
88
|
+
|
|
89
|
+
# Rope project settings
|
|
90
|
+
.ropeproject
|
|
91
|
+
|
|
92
|
+
# mkdocs documentation
|
|
93
|
+
/site
|
|
94
|
+
|
|
95
|
+
# mypy
|
|
96
|
+
.mypy_cache/
|
|
97
|
+
|
|
98
|
+
# ruff
|
|
99
|
+
.ruff_cache/
|
|
100
|
+
|
|
101
|
+
# IDEs
|
|
102
|
+
.idea/
|
|
103
|
+
.vscode/
|
|
104
|
+
|
|
105
|
+
# Mojo extension compile cache (experimental torch-fourier-slice kernels)
|
|
106
|
+
__mojocache__/
|
|
107
|
+
*.mojopkg
|
|
108
|
+
# experimental demo outputs
|
|
109
|
+
packages/primitives/torch-fourier-slice/examples/*.npz
|
|
110
|
+
packages/primitives/torch-fourier-slice/examples/*.png
|
|
111
|
+
packages/primitives/torch-fourier-slice/examples/*.gif
|
|
112
|
+
# Personal local notes (not for commit)
|
|
113
|
+
notes/*.local.md
|
|
114
|
+
|
|
115
|
+
lightning_logs/
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020, TeamTomo
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: torch-structure-manipulation
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Typed molecular structure data, bonding annotations, and tensor transforms
|
|
5
|
+
Project-URL: homepage, https://github.com/teamtomo/teamtomo
|
|
6
|
+
Project-URL: repository, https://github.com/teamtomo/teamtomo
|
|
7
|
+
Author: TeamTomo Developers
|
|
8
|
+
Author-email: Davide Torre <davidetorre99@gmail.com>
|
|
9
|
+
License: BSD-3-Clause
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: gemmi
|
|
21
|
+
Requires-Dist: numpy
|
|
22
|
+
Requires-Dist: pandas
|
|
23
|
+
Requires-Dist: roma
|
|
24
|
+
Requires-Dist: torch
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# torch-structure-manipulation
|
|
28
|
+
|
|
29
|
+
Typed, in-memory utilities for atomic structures:
|
|
30
|
+
|
|
31
|
+
- `AtomicStructure.from_dataframe` converts mmdf-compatible DataFrames into
|
|
32
|
+
device-aware tensors, with positions stored in `(z, y, x)` array order.
|
|
33
|
+
- `AtomicStructure.from_annotated_dataframe` annotates bonding metadata and
|
|
34
|
+
then constructs the structure in one step.
|
|
35
|
+
- `annotate_bonding_environments` adds template-derived bonding and
|
|
36
|
+
`protein`/`rna`/`other` molecule annotations without reading files.
|
|
37
|
+
- `classify_structure_composition` and `get_scattering_provider_keys` expose
|
|
38
|
+
aggregate composition labels and per-atom Peng provider keys respectively.
|
|
39
|
+
- Centering, rotation, translation, atom selection, and coordinate conversion
|
|
40
|
+
helpers are re-exported from the package root (also available under
|
|
41
|
+
``structure_transforms``).
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import mmdf
|
|
45
|
+
|
|
46
|
+
from torch_structure_manipulation import (
|
|
47
|
+
AtomicStructure,
|
|
48
|
+
annotate_bonding_environments,
|
|
49
|
+
center_structure,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# File I/O belongs to the caller; mmdf is not a runtime dependency.
|
|
53
|
+
atoms = mmdf.read("structure.cif")
|
|
54
|
+
structure = AtomicStructure.from_annotated_dataframe(atoms, include_hydrogens=False)
|
|
55
|
+
|
|
56
|
+
# Or annotate first when you still need the DataFrame:
|
|
57
|
+
annotated = annotate_bonding_environments(atoms)
|
|
58
|
+
centered = center_structure(annotated, center_point=(0.0, 0.0, 0.0), zyx=False)
|
|
59
|
+
structure = AtomicStructure.from_dataframe(centered)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
This project is licensed under the BSD 3-Clause License; see `LICENSE`.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# torch-structure-manipulation
|
|
2
|
+
|
|
3
|
+
Typed, in-memory utilities for atomic structures:
|
|
4
|
+
|
|
5
|
+
- `AtomicStructure.from_dataframe` converts mmdf-compatible DataFrames into
|
|
6
|
+
device-aware tensors, with positions stored in `(z, y, x)` array order.
|
|
7
|
+
- `AtomicStructure.from_annotated_dataframe` annotates bonding metadata and
|
|
8
|
+
then constructs the structure in one step.
|
|
9
|
+
- `annotate_bonding_environments` adds template-derived bonding and
|
|
10
|
+
`protein`/`rna`/`other` molecule annotations without reading files.
|
|
11
|
+
- `classify_structure_composition` and `get_scattering_provider_keys` expose
|
|
12
|
+
aggregate composition labels and per-atom Peng provider keys respectively.
|
|
13
|
+
- Centering, rotation, translation, atom selection, and coordinate conversion
|
|
14
|
+
helpers are re-exported from the package root (also available under
|
|
15
|
+
``structure_transforms``).
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import mmdf
|
|
19
|
+
|
|
20
|
+
from torch_structure_manipulation import (
|
|
21
|
+
AtomicStructure,
|
|
22
|
+
annotate_bonding_environments,
|
|
23
|
+
center_structure,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# File I/O belongs to the caller; mmdf is not a runtime dependency.
|
|
27
|
+
atoms = mmdf.read("structure.cif")
|
|
28
|
+
structure = AtomicStructure.from_annotated_dataframe(atoms, include_hydrogens=False)
|
|
29
|
+
|
|
30
|
+
# Or annotate first when you still need the DataFrame:
|
|
31
|
+
annotated = annotate_bonding_environments(atoms)
|
|
32
|
+
centered = center_structure(annotated, center_point=(0.0, 0.0, 0.0), zyx=False)
|
|
33
|
+
structure = AtomicStructure.from_dataframe(centered)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## License
|
|
37
|
+
|
|
38
|
+
This project is licensed under the BSD 3-Clause License; see `LICENSE`.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[tool.hatch.version]
|
|
6
|
+
source = "vcs"
|
|
7
|
+
tag-pattern = "^torch-structure-manipulation@v(?P<version>.+)$"
|
|
8
|
+
fallback-version = "0.1.0"
|
|
9
|
+
|
|
10
|
+
[tool.hatch.version.raw-options]
|
|
11
|
+
search_parent_directories = true
|
|
12
|
+
tag_regex = "^torch-structure-manipulation@v(?P<version>\\d+\\.\\d+\\.\\d+.*)$"
|
|
13
|
+
git_describe_command = "git describe --dirty --tags --long --match 'torch-structure-manipulation@v[0-9]*.[0-9]*.[0-9]*'"
|
|
14
|
+
|
|
15
|
+
[tool.hatch.build.targets.wheel]
|
|
16
|
+
only-include = ["src"]
|
|
17
|
+
sources = ["src"]
|
|
18
|
+
|
|
19
|
+
[project]
|
|
20
|
+
name = "torch-structure-manipulation"
|
|
21
|
+
dynamic = ["version"]
|
|
22
|
+
description = "Typed molecular structure data, bonding annotations, and tensor transforms"
|
|
23
|
+
readme = "README.md"
|
|
24
|
+
requires-python = ">=3.11"
|
|
25
|
+
license = { text = "BSD-3-Clause" }
|
|
26
|
+
authors = [
|
|
27
|
+
{ name = "Davide Torre", email = "davidetorre99@gmail.com" },
|
|
28
|
+
{ name = "TeamTomo Developers" },
|
|
29
|
+
]
|
|
30
|
+
classifiers = [
|
|
31
|
+
"Development Status :: 3 - Alpha",
|
|
32
|
+
"License :: OSI Approved :: BSD License",
|
|
33
|
+
"Programming Language :: Python :: 3",
|
|
34
|
+
"Programming Language :: Python :: 3.11",
|
|
35
|
+
"Programming Language :: Python :: 3.12",
|
|
36
|
+
"Programming Language :: Python :: 3.13",
|
|
37
|
+
"Programming Language :: Python :: 3.14",
|
|
38
|
+
"Typing :: Typed",
|
|
39
|
+
]
|
|
40
|
+
dependencies = ["gemmi", "numpy", "pandas", "roma", "torch"]
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
homepage = "https://github.com/teamtomo/teamtomo"
|
|
44
|
+
repository = "https://github.com/teamtomo/teamtomo"
|
|
45
|
+
|
|
46
|
+
[dependency-groups]
|
|
47
|
+
test = ["pytest", "pytest-cov"]
|
|
48
|
+
dev = [
|
|
49
|
+
{ include-group = "test" },
|
|
50
|
+
"ipython",
|
|
51
|
+
"mypy",
|
|
52
|
+
"pdbpp",
|
|
53
|
+
"rich",
|
|
54
|
+
"ruff",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[tool.ruff]
|
|
58
|
+
line-length = 88
|
|
59
|
+
target-version = "py311"
|
|
60
|
+
src = ["src"]
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint]
|
|
63
|
+
pydocstyle = { convention = "numpy" }
|
|
64
|
+
select = ["E", "W", "F", "D", "D417", "I", "UP", "C4", "B", "A001", "RUF", "TC", "TID"]
|
|
65
|
+
ignore = ["D401"]
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint.per-file-ignores]
|
|
68
|
+
"tests/*.py" = ["D", "S"]
|
|
69
|
+
|
|
70
|
+
[tool.mypy]
|
|
71
|
+
files = "src/torch_structure_manipulation"
|
|
72
|
+
strict = true
|
|
73
|
+
disallow_any_generics = false
|
|
74
|
+
disallow_subclassing_any = false
|
|
75
|
+
show_error_codes = true
|
|
76
|
+
pretty = true
|
|
77
|
+
|
|
78
|
+
[[tool.mypy.overrides]]
|
|
79
|
+
module = ["pandas", "pandas.*"]
|
|
80
|
+
ignore_missing_imports = true
|
|
81
|
+
|
|
82
|
+
[[tool.mypy.overrides]]
|
|
83
|
+
module = ["roma", "roma.*"]
|
|
84
|
+
ignore_missing_imports = true
|
|
85
|
+
|
|
86
|
+
[tool.pytest.ini_options]
|
|
87
|
+
minversion = "7.0"
|
|
88
|
+
testpaths = ["tests"]
|
|
89
|
+
filterwarnings = ["error"]
|
|
90
|
+
|
|
91
|
+
[tool.coverage.run]
|
|
92
|
+
source = ["torch_structure_manipulation"]
|
|
93
|
+
|
|
94
|
+
[tool.check-manifest]
|
|
95
|
+
ignore = [".ruff_cache/**/*", "tests/**/*"]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Atomic structure data, bonding annotations, and structure transforms."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
from .atomic_structure import AtomicStructure
|
|
6
|
+
from .bonding import (
|
|
7
|
+
annotate_bonding_environments,
|
|
8
|
+
classify_structure_composition,
|
|
9
|
+
get_scattering_provider_keys,
|
|
10
|
+
)
|
|
11
|
+
from .structure_transforms import (
|
|
12
|
+
apply_rotation,
|
|
13
|
+
apply_rotation_to_coords,
|
|
14
|
+
apply_translation,
|
|
15
|
+
apply_translation_to_coords,
|
|
16
|
+
ball_query_atoms,
|
|
17
|
+
calculate_center_from_tensors,
|
|
18
|
+
center_structure,
|
|
19
|
+
center_structure_from_coords,
|
|
20
|
+
df_to_atomxyz,
|
|
21
|
+
df_to_atomzyx,
|
|
22
|
+
find_atoms_in_ball,
|
|
23
|
+
get_nucleic_acid_residues,
|
|
24
|
+
get_protein_residues,
|
|
25
|
+
remove_sidechains,
|
|
26
|
+
separate_protein_rna,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
__version__ = version("torch-structure-manipulation")
|
|
31
|
+
except PackageNotFoundError:
|
|
32
|
+
__version__ = "uninstalled"
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"AtomicStructure",
|
|
36
|
+
"annotate_bonding_environments",
|
|
37
|
+
"apply_rotation",
|
|
38
|
+
"apply_rotation_to_coords",
|
|
39
|
+
"apply_translation",
|
|
40
|
+
"apply_translation_to_coords",
|
|
41
|
+
"ball_query_atoms",
|
|
42
|
+
"calculate_center_from_tensors",
|
|
43
|
+
"center_structure",
|
|
44
|
+
"center_structure_from_coords",
|
|
45
|
+
"classify_structure_composition",
|
|
46
|
+
"df_to_atomxyz",
|
|
47
|
+
"df_to_atomzyx",
|
|
48
|
+
"find_atoms_in_ball",
|
|
49
|
+
"get_nucleic_acid_residues",
|
|
50
|
+
"get_protein_residues",
|
|
51
|
+
"get_scattering_provider_keys",
|
|
52
|
+
"remove_sidechains",
|
|
53
|
+
"separate_protein_rna",
|
|
54
|
+
]
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Tensor-backed atomic structure data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, replace
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import gemmi
|
|
9
|
+
import torch
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class AtomicStructure:
|
|
17
|
+
"""Lightweight numerical representation of an atomic structure.
|
|
18
|
+
|
|
19
|
+
Positions use array-friendly ``(z, y, x)`` order. Numerical fields may have
|
|
20
|
+
arbitrary broadcast-compatible batch dimensions before their atom dimension.
|
|
21
|
+
Text metadata is shared across batches and held in immutable tuples.
|
|
22
|
+
|
|
23
|
+
Bonding metadata (``bonded_environments``, ``molecule_types``) is not batched:
|
|
24
|
+
one tuple is shared by every batch member. That matches ensemble-of-poses use
|
|
25
|
+
cases (same chemistry, different coordinates) but not batched structures with
|
|
26
|
+
different chemistry. See :meth:`from_annotated_dataframe` and the bonded-factor
|
|
27
|
+
notes on
|
|
28
|
+
:func:`torch_calculate_electrostatic_potential.potential_from_structure_3d`.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
positions_zyx: torch.Tensor
|
|
32
|
+
atomic_numbers: torch.Tensor
|
|
33
|
+
elements: tuple[str, ...]
|
|
34
|
+
atom_names: tuple[str, ...]
|
|
35
|
+
b_factors: torch.Tensor
|
|
36
|
+
occupancies: torch.Tensor
|
|
37
|
+
bonded_environments: tuple[str, ...] | None = None
|
|
38
|
+
molecule_types: tuple[str, ...] | None = None
|
|
39
|
+
|
|
40
|
+
def __post_init__(self) -> None:
|
|
41
|
+
"""Validate atom dimensions and batch broadcasting."""
|
|
42
|
+
if self.positions_zyx.ndim < 2 or self.positions_zyx.shape[-1] != 3:
|
|
43
|
+
raise ValueError("positions_zyx must have shape (..., n_atoms, 3)")
|
|
44
|
+
n_atoms = self.positions_zyx.shape[-2]
|
|
45
|
+
if self.atomic_numbers.ndim < 1 or self.atomic_numbers.shape[-1] != n_atoms:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
"atomic_numbers must have shape (..., n_atoms) with the same "
|
|
48
|
+
"number of atoms as positions_zyx"
|
|
49
|
+
)
|
|
50
|
+
if len(self.elements) != n_atoms or len(self.atom_names) != n_atoms:
|
|
51
|
+
raise ValueError("text metadata must have one value per atom")
|
|
52
|
+
numerical_fields = {
|
|
53
|
+
"b_factors": self.b_factors,
|
|
54
|
+
"occupancies": self.occupancies,
|
|
55
|
+
}
|
|
56
|
+
for name, value in numerical_fields.items():
|
|
57
|
+
if value.ndim > 0 and value.shape[-1] != n_atoms:
|
|
58
|
+
raise ValueError(f"{name} must be scalar or have shape (..., n_atoms)")
|
|
59
|
+
batch_shapes = [
|
|
60
|
+
self.positions_zyx.shape[:-2],
|
|
61
|
+
self.atomic_numbers.shape[:-1],
|
|
62
|
+
*[
|
|
63
|
+
value.shape[:-1] if value.ndim > 0 else ()
|
|
64
|
+
for value in numerical_fields.values()
|
|
65
|
+
],
|
|
66
|
+
]
|
|
67
|
+
try:
|
|
68
|
+
torch.broadcast_shapes(*batch_shapes) # type: ignore[no-untyped-call]
|
|
69
|
+
except RuntimeError as error:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
"numerical AtomicStructure fields have incompatible batch shapes"
|
|
72
|
+
) from error
|
|
73
|
+
if (
|
|
74
|
+
self.bonded_environments is not None
|
|
75
|
+
and len(self.bonded_environments) != n_atoms
|
|
76
|
+
):
|
|
77
|
+
raise ValueError("bonded_environments must have one value per atom")
|
|
78
|
+
if self.molecule_types is not None and len(self.molecule_types) != n_atoms:
|
|
79
|
+
raise ValueError("molecule_types must have one value per atom")
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_dataframe(
|
|
83
|
+
cls,
|
|
84
|
+
df: pd.DataFrame,
|
|
85
|
+
*,
|
|
86
|
+
device: torch.device | str | None = None,
|
|
87
|
+
dtype: torch.dtype = torch.float32,
|
|
88
|
+
) -> AtomicStructure:
|
|
89
|
+
"""Construct from an mmdf-compatible DataFrame.
|
|
90
|
+
|
|
91
|
+
Required columns are ``x``, ``y``, ``z``, and ``element``. Atom names
|
|
92
|
+
come from ``atom`` when present. ``b_isotropic`` and ``occupancy``
|
|
93
|
+
default to zero and one, respectively. Optional ``bonded_environments``
|
|
94
|
+
and ``molecule_type`` columns are preserved when present.
|
|
95
|
+
"""
|
|
96
|
+
required = {"x", "y", "z", "element"}
|
|
97
|
+
missing = sorted(required.difference(df.columns))
|
|
98
|
+
if missing:
|
|
99
|
+
raise ValueError(f"missing required structure columns: {missing}")
|
|
100
|
+
|
|
101
|
+
elements = tuple(str(value).strip().upper() for value in df["element"])
|
|
102
|
+
if "atomic_number" in df:
|
|
103
|
+
atomic_number_values = [int(value) for value in df["atomic_number"]]
|
|
104
|
+
else:
|
|
105
|
+
atomic_number_values = [
|
|
106
|
+
gemmi.Element(element).atomic_number for element in elements
|
|
107
|
+
]
|
|
108
|
+
unknown = sorted(
|
|
109
|
+
element
|
|
110
|
+
for element, atomic_number in zip(
|
|
111
|
+
elements, atomic_number_values, strict=True
|
|
112
|
+
)
|
|
113
|
+
if atomic_number == 0
|
|
114
|
+
)
|
|
115
|
+
if unknown:
|
|
116
|
+
raise ValueError(f"unknown element symbols: {unknown}")
|
|
117
|
+
|
|
118
|
+
positions = torch.as_tensor(
|
|
119
|
+
df.loc[:, ["z", "y", "x"]].to_numpy(copy=True),
|
|
120
|
+
dtype=dtype,
|
|
121
|
+
device=device,
|
|
122
|
+
)
|
|
123
|
+
atomic_numbers = torch.tensor(
|
|
124
|
+
atomic_number_values,
|
|
125
|
+
dtype=torch.int64,
|
|
126
|
+
device=device,
|
|
127
|
+
)
|
|
128
|
+
b_values = df["b_isotropic"] if "b_isotropic" in df else [0.0] * len(df)
|
|
129
|
+
occupancy_values = df["occupancy"] if "occupancy" in df else [1.0] * len(df)
|
|
130
|
+
atom_names = (
|
|
131
|
+
tuple(str(value).strip() for value in df["atom"])
|
|
132
|
+
if "atom" in df
|
|
133
|
+
else ("",) * len(df)
|
|
134
|
+
)
|
|
135
|
+
bonded = (
|
|
136
|
+
tuple(str(value) for value in df["bonded_environments"])
|
|
137
|
+
if "bonded_environments" in df
|
|
138
|
+
else None
|
|
139
|
+
)
|
|
140
|
+
molecule_types = (
|
|
141
|
+
tuple(str(value) for value in df["molecule_type"])
|
|
142
|
+
if "molecule_type" in df
|
|
143
|
+
else None
|
|
144
|
+
)
|
|
145
|
+
return cls(
|
|
146
|
+
positions_zyx=positions,
|
|
147
|
+
atomic_numbers=atomic_numbers,
|
|
148
|
+
elements=elements,
|
|
149
|
+
atom_names=atom_names,
|
|
150
|
+
b_factors=torch.as_tensor(b_values, dtype=dtype, device=device),
|
|
151
|
+
occupancies=torch.as_tensor(occupancy_values, dtype=dtype, device=device),
|
|
152
|
+
bonded_environments=bonded,
|
|
153
|
+
molecule_types=molecule_types,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def from_annotated_dataframe(
|
|
158
|
+
cls,
|
|
159
|
+
df: pd.DataFrame,
|
|
160
|
+
*,
|
|
161
|
+
include_hydrogens: bool = True,
|
|
162
|
+
device: torch.device | str | None = None,
|
|
163
|
+
dtype: torch.dtype = torch.float32,
|
|
164
|
+
) -> AtomicStructure:
|
|
165
|
+
"""Annotate bonding metadata, then construct from the result.
|
|
166
|
+
|
|
167
|
+
This is the usual entry point for Peng bonded scattering factors: it
|
|
168
|
+
calls :func:`~torch_structure_manipulation.annotate_bonding_environments`
|
|
169
|
+
to add ``bonded_environments`` and ``molecule_type`` columns, then
|
|
170
|
+
delegates to :meth:`from_dataframe`.
|
|
171
|
+
|
|
172
|
+
The input must include ``chain``, ``residue_id``, ``residue``, ``atom``,
|
|
173
|
+
and ``element`` in addition to the coordinate columns required by
|
|
174
|
+
:meth:`from_dataframe`.
|
|
175
|
+
"""
|
|
176
|
+
from .bonding import annotate_bonding_environments
|
|
177
|
+
|
|
178
|
+
annotated = annotate_bonding_environments(
|
|
179
|
+
df, include_hydrogens=include_hydrogens
|
|
180
|
+
)
|
|
181
|
+
return cls.from_dataframe(annotated, device=device, dtype=dtype)
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def num_atoms(self) -> int:
|
|
185
|
+
"""Number of atoms in each structure."""
|
|
186
|
+
return self.positions_zyx.shape[-2]
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def batch_shape(self) -> torch.Size:
|
|
190
|
+
"""Broadcasted batch shape of all numerical fields."""
|
|
191
|
+
batch_shapes = [
|
|
192
|
+
self.positions_zyx.shape[:-2],
|
|
193
|
+
self.atomic_numbers.shape[:-1],
|
|
194
|
+
self.b_factors.shape[:-1] if self.b_factors.ndim > 0 else (),
|
|
195
|
+
self.occupancies.shape[:-1] if self.occupancies.ndim > 0 else (),
|
|
196
|
+
]
|
|
197
|
+
return torch.Size(
|
|
198
|
+
torch.broadcast_shapes(*batch_shapes) # type: ignore[no-untyped-call]
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def device(self) -> torch.device:
|
|
203
|
+
"""Device containing the atomic positions."""
|
|
204
|
+
return self.positions_zyx.device
|
|
205
|
+
|
|
206
|
+
def with_positions(self, positions_zyx: torch.Tensor) -> AtomicStructure:
|
|
207
|
+
"""Return a copy with replacement, broadcast-compatible positions."""
|
|
208
|
+
if positions_zyx.ndim < 2 or positions_zyx.shape[-2:] != (self.num_atoms, 3):
|
|
209
|
+
raise ValueError(
|
|
210
|
+
"replacement positions must have shape (..., n_atoms, 3) with the "
|
|
211
|
+
"same number of atoms"
|
|
212
|
+
)
|
|
213
|
+
return replace(self, positions_zyx=positions_zyx)
|
|
214
|
+
|
|
215
|
+
def to(
|
|
216
|
+
self,
|
|
217
|
+
device: torch.device | str | None = None,
|
|
218
|
+
dtype: torch.dtype | None = None,
|
|
219
|
+
) -> AtomicStructure:
|
|
220
|
+
"""Return a copy with numerical tensors moved to a device and dtype."""
|
|
221
|
+
floating_dtype = self.positions_zyx.dtype if dtype is None else dtype
|
|
222
|
+
return replace(
|
|
223
|
+
self,
|
|
224
|
+
positions_zyx=self.positions_zyx.to(device=device, dtype=floating_dtype),
|
|
225
|
+
atomic_numbers=self.atomic_numbers.to(device=device),
|
|
226
|
+
b_factors=self.b_factors.to(device=device, dtype=floating_dtype),
|
|
227
|
+
occupancies=self.occupancies.to(device=device, dtype=floating_dtype),
|
|
228
|
+
)
|