vimol 0.1.3__tar.gz → 0.1.4__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.
- vimol-0.1.4/PKG-INFO +142 -0
- vimol-0.1.4/README.md +106 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/__init__.py +1 -1
- vimol-0.1.4/src/vimol/cistrans.py +142 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/viewer.py +370 -110
- vimol-0.1.4/src/vimol.egg-info/PKG-INFO +142 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol.egg-info/SOURCES.txt +2 -0
- vimol-0.1.4/tests/test_cistrans.py +146 -0
- {vimol-0.1.3 → vimol-0.1.4}/tests/test_vimol.py +505 -115
- vimol-0.1.3/PKG-INFO +0 -199
- vimol-0.1.3/README.md +0 -163
- vimol-0.1.3/src/vimol.egg-info/PKG-INFO +0 -199
- {vimol-0.1.3 → vimol-0.1.4}/LICENSE +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/pyproject.toml +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/setup.cfg +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/__main__.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/_render_fast.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/align.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/app.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/arrows.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/bonds.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/camera.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/data/c60.xyz +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/editor.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/elements.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/file_browser.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/gl_adapter.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/gl_render.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/glyph_font.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/glyph_mesh.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/glyph_outlines.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/glyphs.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/input.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/kitty.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/molecule.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/parsers/__init__.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/parsers/mol.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/parsers/pdb.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/parsers/xyz.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/periodic_table.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/py.typed +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/render.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/render_glyph.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/residues.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/scene.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/select.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/structures.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/templates.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/theme.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol/widget.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol.egg-info/dependency_links.txt +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol.egg-info/entry_points.txt +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol.egg-info/requires.txt +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/src/vimol.egg-info/top_level.txt +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/tests/test_add_file.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/tests/test_align.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/tests/test_bonds.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/tests/test_editing.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/tests/test_file_browser.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/tests/test_gl_render.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/tests/test_glyphs.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/tests/test_render_fast.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/tests/test_scene_structures.py +0 -0
- {vimol-0.1.3 → vimol-0.1.4}/tests/test_structures.py +0 -0
vimol-0.1.4/PKG-INFO
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vimol
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: A terminal molecular viewer and editor using the Kitty graphics protocol
|
|
5
|
+
Author: vimol contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: molecular,viewer,editor,terminal,kitty,chemistry,visualization,xyz,pdb,sdf,cli,tui
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
23
|
+
Classifier: Topic :: Multimedia :: Graphics :: Viewers
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: numpy>=1.20
|
|
29
|
+
Requires-Dist: moderngl>=5.6
|
|
30
|
+
Requires-Dist: numba>=0.59
|
|
31
|
+
Provides-Extra: test
|
|
32
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
33
|
+
Provides-Extra: align
|
|
34
|
+
Requires-Dist: scipy>=1.4; extra == "align"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# vimol
|
|
38
|
+
|
|
39
|
+
A molecular viewer and editor that runs **in your terminal**. Open an `.xyz`,
|
|
40
|
+
`.pdb`, `.mol` or `.sdf` file and get a shaded, rotatable, editable 3D
|
|
41
|
+
structure right where you are already working — over SSH, in tmux, beside your
|
|
42
|
+
editor.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install vimol
|
|
46
|
+
vimol molecule.xyz
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
That is the C60 bundled with vimol, which `vimol` opens with no arguments.
|
|
52
|
+
Drag to rotate, scroll to zoom, option-drag empty space to pan, hover an atom
|
|
53
|
+
to identify it, `?` for every binding.
|
|
54
|
+
|
|
55
|
+
You need Python 3.8 or newer and a terminal that speaks the
|
|
56
|
+
[Kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/) —
|
|
57
|
+
**kitty, Ghostty or WezTerm**. Rendering is pure software, with an OpenGL path
|
|
58
|
+
used automatically where a GPU is available. MIT licensed.
|
|
59
|
+
|
|
60
|
+
## Proteins out of bare coordinates
|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
An `.xyz` file has no residue names, no atom names and no chains. vimol finds
|
|
65
|
+
the backbone anyway, opens the file as a ribbon, and on `6` letters every
|
|
66
|
+
residue onto it. The trp-cage above is PDB 1RIJ stripped to bare coordinates,
|
|
67
|
+
which vimol reads back as `ALQELLGQWLKDGGPSSGRPPPS` — character for character
|
|
68
|
+
the sequence in that entry.
|
|
69
|
+
|
|
70
|
+
## Overlays and RMSD
|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
Open several files, or a trajectory, and every structure lands in one session,
|
|
75
|
+
overlaid and colour-coded down the left. `r` aligns the whole overlay onto the
|
|
76
|
+
active structure and reports each RMSD in a column beside the list — above, a
|
|
77
|
+
chignolin crystal structure and 72 CREST conformers of it. `R` runs the same
|
|
78
|
+
fit on only the atoms you pick.
|
|
79
|
+
|
|
80
|
+
## Editing
|
|
81
|
+
|
|
82
|
+

|
|
83
|
+
|
|
84
|
+
Editing is on by default. Click a hydrogen to grow a fragment in its place, a
|
|
85
|
+
heavy atom to swap the element, empty space to start something new — one click
|
|
86
|
+
turns threonine into O-methylthreonine. Bonds, deletion, clash cleanup, undo
|
|
87
|
+
and save are a keystroke each.
|
|
88
|
+
|
|
89
|
+
## As a library
|
|
90
|
+
|
|
91
|
+

|
|
92
|
+
|
|
93
|
+
Three lines put a molecule anywhere on the screen:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import os, vimol
|
|
97
|
+
|
|
98
|
+
scene = vimol.Scene(vimol.load("c60.xyz"), 640, 380) # render it off-screen
|
|
99
|
+
scene.style.transparent = True # let the terminal through
|
|
100
|
+
os.write(1, b"\x1b[6;19H" + scene.to_kitty()) # paint at row 6, col 19
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`scene.render()` hands you the pixels as a numpy array instead and
|
|
104
|
+
`scene.to_png()` writes a file. To let the reader turn the molecule,
|
|
105
|
+
`MoleculeWidget` takes the input events you hand it and leaves the loop to you;
|
|
106
|
+
`examples/embed_demo.py` is a complete host application built that way.
|
|
107
|
+
|
|
108
|
+
## Reference
|
|
109
|
+
|
|
110
|
+
| Key | Does |
|
|
111
|
+
|---|---|
|
|
112
|
+
| Drag / arrows / `hjkl` | rotate |
|
|
113
|
+
| Wheel, `+` `-` | zoom |
|
|
114
|
+
| Right or middle drag | pan |
|
|
115
|
+
| `[` `]` | roll |
|
|
116
|
+
| `1` `2` `3` `4` | ball-and-stick, space-filling, licorice, wireframe |
|
|
117
|
+
| `5` `6` | ribbon, lettered residues (proteins) |
|
|
118
|
+
| `f` `z` | re-fit, reset the view |
|
|
119
|
+
| `m` | measure — click 2, 3 or 4 atoms for distance, angle, dihedral |
|
|
120
|
+
| `a` `x` `c` `u` `s` | append, delete, cleanup clashes, undo, save |
|
|
121
|
+
| `r` `R` | align an overlay, or align on picked atoms |
|
|
122
|
+
| `Shift+S` | ready-made selections: backbone, heavy atoms, ring system |
|
|
123
|
+
| `A` `n` `p` | add a file, next and previous structure |
|
|
124
|
+
| `Tab` | focus the structure list |
|
|
125
|
+
| `d` `g` `t` `ctrl-t` | depth cue, hi-quality, transparent background, theme |
|
|
126
|
+
| `?` `q` | help, quit |
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
vimol traj.xyz --spin --style spacefill # spin a trajectory, space-filling
|
|
130
|
+
vimol protein.pdb --style glyph # lettered residues on a ribbon
|
|
131
|
+
vimol a.xyz b.pdb --style ribbon # overlay two proteins
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Several files always open in ball-and-stick, so pass `--style ribbon` for
|
|
135
|
+
proteins. Also `--backend cpu|gl|auto`, `--theme dark|light|auto`,
|
|
136
|
+
`--rotate YAW PITCH`, `--frame N`, `--background`,
|
|
137
|
+
`--transparent`/`--opaque`, `--atom-scale`, `--bond-radius`,
|
|
138
|
+
`--no-depth-cue`, `--no-bonds`, `--bond-tolerance`, `--list-formats`.
|
|
139
|
+
`pip install vimol[align]` adds scipy, which speeds up subset alignment.
|
|
140
|
+
|
|
141
|
+
The protein, alignment and editing animations are recorded by
|
|
142
|
+
`scripts/record_demos.py`.
|
vimol-0.1.4/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# vimol
|
|
2
|
+
|
|
3
|
+
A molecular viewer and editor that runs **in your terminal**. Open an `.xyz`,
|
|
4
|
+
`.pdb`, `.mol` or `.sdf` file and get a shaded, rotatable, editable 3D
|
|
5
|
+
structure right where you are already working — over SSH, in tmux, beside your
|
|
6
|
+
editor.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install vimol
|
|
10
|
+
vimol molecule.xyz
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
That is the C60 bundled with vimol, which `vimol` opens with no arguments.
|
|
16
|
+
Drag to rotate, scroll to zoom, option-drag empty space to pan, hover an atom
|
|
17
|
+
to identify it, `?` for every binding.
|
|
18
|
+
|
|
19
|
+
You need Python 3.8 or newer and a terminal that speaks the
|
|
20
|
+
[Kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/) —
|
|
21
|
+
**kitty, Ghostty or WezTerm**. Rendering is pure software, with an OpenGL path
|
|
22
|
+
used automatically where a GPU is available. MIT licensed.
|
|
23
|
+
|
|
24
|
+
## Proteins out of bare coordinates
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
An `.xyz` file has no residue names, no atom names and no chains. vimol finds
|
|
29
|
+
the backbone anyway, opens the file as a ribbon, and on `6` letters every
|
|
30
|
+
residue onto it. The trp-cage above is PDB 1RIJ stripped to bare coordinates,
|
|
31
|
+
which vimol reads back as `ALQELLGQWLKDGGPSSGRPPPS` — character for character
|
|
32
|
+
the sequence in that entry.
|
|
33
|
+
|
|
34
|
+
## Overlays and RMSD
|
|
35
|
+
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
Open several files, or a trajectory, and every structure lands in one session,
|
|
39
|
+
overlaid and colour-coded down the left. `r` aligns the whole overlay onto the
|
|
40
|
+
active structure and reports each RMSD in a column beside the list — above, a
|
|
41
|
+
chignolin crystal structure and 72 CREST conformers of it. `R` runs the same
|
|
42
|
+
fit on only the atoms you pick.
|
|
43
|
+
|
|
44
|
+
## Editing
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
Editing is on by default. Click a hydrogen to grow a fragment in its place, a
|
|
49
|
+
heavy atom to swap the element, empty space to start something new — one click
|
|
50
|
+
turns threonine into O-methylthreonine. Bonds, deletion, clash cleanup, undo
|
|
51
|
+
and save are a keystroke each.
|
|
52
|
+
|
|
53
|
+
## As a library
|
|
54
|
+
|
|
55
|
+

|
|
56
|
+
|
|
57
|
+
Three lines put a molecule anywhere on the screen:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import os, vimol
|
|
61
|
+
|
|
62
|
+
scene = vimol.Scene(vimol.load("c60.xyz"), 640, 380) # render it off-screen
|
|
63
|
+
scene.style.transparent = True # let the terminal through
|
|
64
|
+
os.write(1, b"\x1b[6;19H" + scene.to_kitty()) # paint at row 6, col 19
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`scene.render()` hands you the pixels as a numpy array instead and
|
|
68
|
+
`scene.to_png()` writes a file. To let the reader turn the molecule,
|
|
69
|
+
`MoleculeWidget` takes the input events you hand it and leaves the loop to you;
|
|
70
|
+
`examples/embed_demo.py` is a complete host application built that way.
|
|
71
|
+
|
|
72
|
+
## Reference
|
|
73
|
+
|
|
74
|
+
| Key | Does |
|
|
75
|
+
|---|---|
|
|
76
|
+
| Drag / arrows / `hjkl` | rotate |
|
|
77
|
+
| Wheel, `+` `-` | zoom |
|
|
78
|
+
| Right or middle drag | pan |
|
|
79
|
+
| `[` `]` | roll |
|
|
80
|
+
| `1` `2` `3` `4` | ball-and-stick, space-filling, licorice, wireframe |
|
|
81
|
+
| `5` `6` | ribbon, lettered residues (proteins) |
|
|
82
|
+
| `f` `z` | re-fit, reset the view |
|
|
83
|
+
| `m` | measure — click 2, 3 or 4 atoms for distance, angle, dihedral |
|
|
84
|
+
| `a` `x` `c` `u` `s` | append, delete, cleanup clashes, undo, save |
|
|
85
|
+
| `r` `R` | align an overlay, or align on picked atoms |
|
|
86
|
+
| `Shift+S` | ready-made selections: backbone, heavy atoms, ring system |
|
|
87
|
+
| `A` `n` `p` | add a file, next and previous structure |
|
|
88
|
+
| `Tab` | focus the structure list |
|
|
89
|
+
| `d` `g` `t` `ctrl-t` | depth cue, hi-quality, transparent background, theme |
|
|
90
|
+
| `?` `q` | help, quit |
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
vimol traj.xyz --spin --style spacefill # spin a trajectory, space-filling
|
|
94
|
+
vimol protein.pdb --style glyph # lettered residues on a ribbon
|
|
95
|
+
vimol a.xyz b.pdb --style ribbon # overlay two proteins
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Several files always open in ball-and-stick, so pass `--style ribbon` for
|
|
99
|
+
proteins. Also `--backend cpu|gl|auto`, `--theme dark|light|auto`,
|
|
100
|
+
`--rotate YAW PITCH`, `--frame N`, `--background`,
|
|
101
|
+
`--transparent`/`--opaque`, `--atom-scale`, `--bond-radius`,
|
|
102
|
+
`--no-depth-cue`, `--no-bonds`, `--bond-tolerance`, `--list-formats`.
|
|
103
|
+
`pip install vimol[align]` adds scipy, which speeds up subset alignment.
|
|
104
|
+
|
|
105
|
+
The protein, alignment and editing animations are recorded by
|
|
106
|
+
`scripts/record_demos.py`.
|
|
@@ -36,7 +36,7 @@ from . import select
|
|
|
36
36
|
from .structures import StructureSet, Structure, Transform, AlignmentResult
|
|
37
37
|
from .align import kabsch, superpose, permutation_search, subset_search
|
|
38
38
|
|
|
39
|
-
__version__ = "0.1.
|
|
39
|
+
__version__ = "0.1.4"
|
|
40
40
|
|
|
41
41
|
__all__ = [
|
|
42
42
|
"Molecule", "VectorField", "Camera", "Renderer", "Style", "Scene", "MoleculeWidget",
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Cis/trans classification of peptide and peptoid backbone bonds.
|
|
2
|
+
|
|
3
|
+
A peptide bond's rotation is described by the omega dihedral,
|
|
4
|
+
``Cα(i)-C(i)-N(i+1)-Cα(i+1)``: near 0° it is cis, near 180° trans. This is
|
|
5
|
+
the same walk :func:`vimol.select.peptide_motifs` already builds for the
|
|
6
|
+
ribbon/RMSD backbone presets, and it does not depend on where a residue's
|
|
7
|
+
side chain attaches -- a peptoid's N-substituted backbone (no amide H) is
|
|
8
|
+
the same N-Cα-C path a peptide's is, so no separate peptoid handling is
|
|
9
|
+
needed here.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import math
|
|
14
|
+
import re
|
|
15
|
+
from typing import List, Optional, Sequence, Tuple
|
|
16
|
+
|
|
17
|
+
from .editor import measure_value
|
|
18
|
+
from .molecule import Molecule
|
|
19
|
+
# The private variant: classification needs the bond graph alongside the
|
|
20
|
+
# runs to notice a cyclic backbone's ring-closing peptide bond, which the
|
|
21
|
+
# public runs-only view cannot express.
|
|
22
|
+
from .select import _peptide_motifs
|
|
23
|
+
|
|
24
|
+
# |omega| below this is cis, at or above it is trans -- the standard
|
|
25
|
+
# literature convention, and a strict binary split with no third bucket.
|
|
26
|
+
_CIS_MAX_ABS_OMEGA = 90.0
|
|
27
|
+
|
|
28
|
+
# Boltzmann population of each classification across a multi-frame file,
|
|
29
|
+
# weighted by the energy in each frame's XYZ comment line. Energies are
|
|
30
|
+
# always read as Hartree (the convention CREST/xtb/ORCA write when the
|
|
31
|
+
# comment is a bare or labeled electronic energy) -- there is deliberately
|
|
32
|
+
# no unit auto-detection: a silently wrong unit produces a distribution that
|
|
33
|
+
# still looks plausible (collapsed onto one conformer, or smeared flat),
|
|
34
|
+
# so getting it wrong is worse than not offering the feature at all.
|
|
35
|
+
TEMPERATURE_K = 298.15
|
|
36
|
+
_KB_HARTREE_PER_K = 3.166811563e-6
|
|
37
|
+
_KT_HARTREE = _KB_HARTREE_PER_K * TEMPERATURE_K
|
|
38
|
+
|
|
39
|
+
# A bare float is CREST's own convention (the whole comment IS the energy).
|
|
40
|
+
_BARE_FLOAT_RE = re.compile(r"^[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$")
|
|
41
|
+
# "energy", "energy:", "energy=", "E", "E=" ... followed by the number --
|
|
42
|
+
# the xtb/ORCA-style labeled convention.
|
|
43
|
+
_LABELED_ENERGY_RE = re.compile(
|
|
44
|
+
r"(?:^|\s)(?:energy|e)\s*[:=]?\s*([+-]?\d+\.\d+(?:[eE][+-]?\d+)?)",
|
|
45
|
+
re.IGNORECASE)
|
|
46
|
+
# Fallback: a decimal-point number anywhere in the comment (excludes bare
|
|
47
|
+
# integers like a model/frame index, which a comment often also carries).
|
|
48
|
+
_DECIMAL_RE = re.compile(r"[+-]?\d+\.\d+(?:[eE][+-]?\d+)?")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_energy(comment: str) -> Optional[float]:
|
|
52
|
+
"""Pull one energy value out of an XYZ comment line, or ``None``.
|
|
53
|
+
|
|
54
|
+
Three conventions, tried in order of confidence: the whole comment is
|
|
55
|
+
the number (CREST); a label ("energy"/"E", "=" or ":" optional)
|
|
56
|
+
precedes it (xtb, ORCA); or exactly one decimal number appears
|
|
57
|
+
anywhere in the comment. Two or more equally plausible numbers with no
|
|
58
|
+
label to disambiguate them is not a confident read -- ``None``, same
|
|
59
|
+
as a comment with no number at all.
|
|
60
|
+
"""
|
|
61
|
+
text = comment.strip()
|
|
62
|
+
if not text:
|
|
63
|
+
return None
|
|
64
|
+
if _BARE_FLOAT_RE.match(text):
|
|
65
|
+
return float(text)
|
|
66
|
+
labeled = _LABELED_ENERGY_RE.search(text)
|
|
67
|
+
if labeled:
|
|
68
|
+
return float(labeled.group(1))
|
|
69
|
+
decimals = _DECIMAL_RE.findall(text)
|
|
70
|
+
if len(decimals) == 1:
|
|
71
|
+
return float(decimals[0])
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def boltzmann_breakdown(classifications: Sequence[str],
|
|
76
|
+
energies_hartree: Sequence[float],
|
|
77
|
+
min_pct: float = 1.0) -> List[Tuple[str, float]]:
|
|
78
|
+
"""Boltzmann-weighted population of each distinct classification string.
|
|
79
|
+
|
|
80
|
+
``classifications`` and ``energies_hartree`` are parallel, one pair per
|
|
81
|
+
frame -- every frame must already have a real (non-``None``)
|
|
82
|
+
classification and energy; filtering that is the caller's job, not
|
|
83
|
+
this function's. Weights are relative to the group's own minimum
|
|
84
|
+
energy (numerically stable, and the group's reference point is the
|
|
85
|
+
only one that matters for population *within* the group). Returns
|
|
86
|
+
``(classification, percentage)`` pairs, rounded to nothing here (the
|
|
87
|
+
caller rounds for display), sorted by descending population, with
|
|
88
|
+
anything under ``min_pct`` dropped -- population that round to 100%
|
|
89
|
+
across the survivors is not guaranteed, by design.
|
|
90
|
+
"""
|
|
91
|
+
if not classifications or len(classifications) != len(energies_hartree):
|
|
92
|
+
return []
|
|
93
|
+
e_min = min(energies_hartree)
|
|
94
|
+
weights = [math.exp(-(e - e_min) / _KT_HARTREE) for e in energies_hartree]
|
|
95
|
+
total = sum(weights)
|
|
96
|
+
populations: dict = {}
|
|
97
|
+
for cls, w in zip(classifications, weights):
|
|
98
|
+
populations[cls] = populations.get(cls, 0.0) + w
|
|
99
|
+
breakdown = [(cls, 100.0 * w / total) for cls, w in populations.items()]
|
|
100
|
+
breakdown = [item for item in breakdown if item[1] >= min_pct]
|
|
101
|
+
breakdown.sort(key=lambda item: item[1], reverse=True)
|
|
102
|
+
return breakdown
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def classify_cis_trans(molecule: Molecule) -> Optional[str]:
|
|
106
|
+
"""One ``c``/``t`` character per peptide bond, chain by chain.
|
|
107
|
+
|
|
108
|
+
A linear chain of N residues yields N-1 characters, one per
|
|
109
|
+
Cα(i)-C(i)-N(i+1) junction; a *cyclic* chain yields N, the extra one
|
|
110
|
+
for the ring-closing bond back from the last residue's carbonyl to the
|
|
111
|
+
first residue's nitrogen -- omitting it would silently merge cyclic
|
|
112
|
+
conformers that differ only in that omega. Multiple chains (a real
|
|
113
|
+
chain break, not just a gap in the file) join with ``/``, in the order
|
|
114
|
+
the motif walk returns them. A chain of a single residue has no
|
|
115
|
+
peptide bond to classify and is dropped; ``None`` means nothing in the
|
|
116
|
+
molecule survived that filter.
|
|
117
|
+
"""
|
|
118
|
+
runs, neighbors, _symbols = _peptide_motifs(molecule)
|
|
119
|
+
chains = []
|
|
120
|
+
|
|
121
|
+
def omega_char(ca_i: int, c_i: int, n_j: int, ca_j: int) -> str:
|
|
122
|
+
omega = measure_value(molecule, (ca_i, c_i, n_j, ca_j))
|
|
123
|
+
return ("c" if omega is not None and abs(omega) < _CIS_MAX_ABS_OMEGA
|
|
124
|
+
else "t")
|
|
125
|
+
|
|
126
|
+
for run in runs:
|
|
127
|
+
if len(run) < 2:
|
|
128
|
+
continue
|
|
129
|
+
chars = [omega_char(ca_i, c_i, n_j, ca_j)
|
|
130
|
+
for (_n_i, ca_i, c_i, _o_i), (n_j, ca_j, _c_j, _o_j)
|
|
131
|
+
in zip(run, run[1:])]
|
|
132
|
+
first_n, first_ca = run[0][0], run[0][1]
|
|
133
|
+
last_ca, last_c = run[-1][1], run[-1][2]
|
|
134
|
+
if first_n in neighbors[last_c]:
|
|
135
|
+
chars.append(omega_char(last_ca, last_c, first_n, first_ca))
|
|
136
|
+
chains.append("".join(chars))
|
|
137
|
+
if not chains:
|
|
138
|
+
return None
|
|
139
|
+
return "/".join(chains)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
__all__ = ["classify_cis_trans", "parse_energy", "boltzmann_breakdown", "TEMPERATURE_K"]
|