unleap 0.1.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.
- unleap-0.1.0/LICENSE +21 -0
- unleap-0.1.0/MANIFEST.in +4 -0
- unleap-0.1.0/PKG-INFO +84 -0
- unleap-0.1.0/README.md +53 -0
- unleap-0.1.0/docs/Makefile +13 -0
- unleap-0.1.0/docs/api.rst +48 -0
- unleap-0.1.0/docs/conf.py +46 -0
- unleap-0.1.0/docs/dev/amber_notes.rst +85 -0
- unleap-0.1.0/docs/dev/architecture.rst +60 -0
- unleap-0.1.0/docs/dev/diff_rules.rst +43 -0
- unleap-0.1.0/docs/dev/extending.rst +35 -0
- unleap-0.1.0/docs/dev/testing.rst +68 -0
- unleap-0.1.0/docs/index.rst +46 -0
- unleap-0.1.0/docs/requirements.txt +2 -0
- unleap-0.1.0/docs/user/installation.rst +41 -0
- unleap-0.1.0/docs/user/limitations.rst +44 -0
- unleap-0.1.0/docs/user/options.rst +95 -0
- unleap-0.1.0/docs/user/outputs.rst +44 -0
- unleap-0.1.0/docs/user/quickstart.rst +71 -0
- unleap-0.1.0/docs/user/validation.rst +49 -0
- unleap-0.1.0/pyproject.toml +47 -0
- unleap-0.1.0/setup.cfg +4 -0
- unleap-0.1.0/tests/conftest.py +72 -0
- unleap-0.1.0/tests/test_unleap.py +222 -0
- unleap-0.1.0/unleap/__init__.py +7 -0
- unleap-0.1.0/unleap/amber.py +125 -0
- unleap-0.1.0/unleap/cli.py +126 -0
- unleap-0.1.0/unleap/extract.py +215 -0
- unleap-0.1.0/unleap/frcmod.py +250 -0
- unleap-0.1.0/unleap/lib.py +11 -0
- unleap-0.1.0/unleap/mol2.py +66 -0
- unleap-0.1.0/unleap/selection.py +57 -0
- unleap-0.1.0/unleap/validate.py +126 -0
- unleap-0.1.0/unleap.egg-info/PKG-INFO +84 -0
- unleap-0.1.0/unleap.egg-info/SOURCES.txt +37 -0
- unleap-0.1.0/unleap.egg-info/dependency_links.txt +1 -0
- unleap-0.1.0/unleap.egg-info/entry_points.txt +2 -0
- unleap-0.1.0/unleap.egg-info/requires.txt +11 -0
- unleap-0.1.0/unleap.egg-info/top_level.txt +1 -0
unleap-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zeke Piskulich
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
unleap-0.1.0/MANIFEST.in
ADDED
unleap-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: unleap
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Recover a ligand's mol2, frcmod and OFF lib from a built Amber parm7/rst7 system
|
|
5
|
+
Author-email: Zeke Piskulich <piskulichz@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/piskuliche/unleap
|
|
8
|
+
Project-URL: Documentation, https://piskuliche.github.io/unleap/
|
|
9
|
+
Project-URL: Issues, https://github.com/piskuliche/unleap/issues
|
|
10
|
+
Keywords: amber,ambertools,tleap,gaff,force field,frcmod,ligand,molecular dynamics
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: parmed
|
|
22
|
+
Requires-Dist: rdkit
|
|
23
|
+
Requires-Dist: numpy
|
|
24
|
+
Requires-Dist: scipy
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: pytest; extra == "test"
|
|
27
|
+
Provides-Extra: docs
|
|
28
|
+
Requires-Dist: sphinx>=7; extra == "docs"
|
|
29
|
+
Requires-Dist: sphinx-rtd-theme>=2; extra == "docs"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# unleap
|
|
33
|
+
|
|
34
|
+
[](https://github.com/piskuliche/unleap/actions/workflows/tests.yml)
|
|
35
|
+
[](https://piskuliche.github.io/unleap/)
|
|
36
|
+
[](https://pypi.org/project/unleap/)
|
|
37
|
+
|
|
38
|
+
**Get a ligand's parameter files back out of a built Amber system.**
|
|
39
|
+
|
|
40
|
+
tleap turns a ligand's `mol2` / `frcmod` / `lib` into a system (`parm7` + `rst7`); `unleap` goes
|
|
41
|
+
the other way. It strips the ligand out of a complex or solvated system and regenerates its
|
|
42
|
+
`frcmod` (a diff against GAFF/GAFF2, plus a self-contained copy), OFF `lib`, `mol2`, and an `sdf`
|
|
43
|
+
for RDKit. It then rebuilds the ligand with tleap to prove the files reproduce the original
|
|
44
|
+
parameters and energies.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
unleap needs AmberTools (tleap, sander), which is not pip-installable. The simplest setup:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
conda create -n unleap -c conda-forge ambertools rdkit
|
|
52
|
+
conda activate unleap
|
|
53
|
+
pip install unleap
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Use
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
unleap -p complex.parm7 -c complex.rst7 -o lig_out # ligand auto-detected
|
|
60
|
+
unleap -p unisc.parm7 -c 0.00000000_ti.rst7 --resname L00 -o L00_out # RBFE dual topology
|
|
61
|
+
unleap -p complex.parm7 -c traj.nc --frame -1 -o lig_out # from a trajectory
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Hydrogen mass repartitioning is detected and undone. The output directory gets `LIG.frcmod`,
|
|
65
|
+
`LIG.full.frcmod`, `LIG.lib`, `LIG.mol2`, `LIG.sdf`, a `tleap.in` that loads them, and a
|
|
66
|
+
`report.txt` with the PASS/FAIL validation results.
|
|
67
|
+
|
|
68
|
+
## Documentation
|
|
69
|
+
|
|
70
|
+
**<https://piskuliche.github.io/unleap/>**: the user guide, developer guide and API reference.
|
|
71
|
+
|
|
72
|
+
To build it locally: `pip install -e ".[docs]" && make -C docs html`.
|
|
73
|
+
|
|
74
|
+
## Development
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git clone https://github.com/piskuliche/unleap.git && cd unleap
|
|
78
|
+
pip install -e ".[test,docs]"
|
|
79
|
+
python -m pytest -q # needs AmberTools on PATH; skipped otherwise
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
unleap-0.1.0/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# unleap
|
|
2
|
+
|
|
3
|
+
[](https://github.com/piskuliche/unleap/actions/workflows/tests.yml)
|
|
4
|
+
[](https://piskuliche.github.io/unleap/)
|
|
5
|
+
[](https://pypi.org/project/unleap/)
|
|
6
|
+
|
|
7
|
+
**Get a ligand's parameter files back out of a built Amber system.**
|
|
8
|
+
|
|
9
|
+
tleap turns a ligand's `mol2` / `frcmod` / `lib` into a system (`parm7` + `rst7`); `unleap` goes
|
|
10
|
+
the other way. It strips the ligand out of a complex or solvated system and regenerates its
|
|
11
|
+
`frcmod` (a diff against GAFF/GAFF2, plus a self-contained copy), OFF `lib`, `mol2`, and an `sdf`
|
|
12
|
+
for RDKit. It then rebuilds the ligand with tleap to prove the files reproduce the original
|
|
13
|
+
parameters and energies.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
unleap needs AmberTools (tleap, sander), which is not pip-installable. The simplest setup:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
conda create -n unleap -c conda-forge ambertools rdkit
|
|
21
|
+
conda activate unleap
|
|
22
|
+
pip install unleap
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Use
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
unleap -p complex.parm7 -c complex.rst7 -o lig_out # ligand auto-detected
|
|
29
|
+
unleap -p unisc.parm7 -c 0.00000000_ti.rst7 --resname L00 -o L00_out # RBFE dual topology
|
|
30
|
+
unleap -p complex.parm7 -c traj.nc --frame -1 -o lig_out # from a trajectory
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Hydrogen mass repartitioning is detected and undone. The output directory gets `LIG.frcmod`,
|
|
34
|
+
`LIG.full.frcmod`, `LIG.lib`, `LIG.mol2`, `LIG.sdf`, a `tleap.in` that loads them, and a
|
|
35
|
+
`report.txt` with the PASS/FAIL validation results.
|
|
36
|
+
|
|
37
|
+
## Documentation
|
|
38
|
+
|
|
39
|
+
**<https://piskuliche.github.io/unleap/>**: the user guide, developer guide and API reference.
|
|
40
|
+
|
|
41
|
+
To build it locally: `pip install -e ".[docs]" && make -C docs html`.
|
|
42
|
+
|
|
43
|
+
## Development
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git clone https://github.com/piskuliche/unleap.git && cd unleap
|
|
47
|
+
pip install -e ".[test,docs]"
|
|
48
|
+
python -m pytest -q # needs AmberTools on PATH; skipped otherwise
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Minimal Sphinx makefile
|
|
2
|
+
SPHINXOPTS ?=
|
|
3
|
+
SPHINXBUILD ?= sphinx-build
|
|
4
|
+
SOURCEDIR = .
|
|
5
|
+
BUILDDIR = _build
|
|
6
|
+
|
|
7
|
+
help:
|
|
8
|
+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
9
|
+
|
|
10
|
+
.PHONY: help Makefile
|
|
11
|
+
|
|
12
|
+
%: Makefile
|
|
13
|
+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
API reference
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
.. automodule:: unleap
|
|
5
|
+
:members: UnleapError
|
|
6
|
+
|
|
7
|
+
Command line
|
|
8
|
+
------------
|
|
9
|
+
|
|
10
|
+
.. automodule:: unleap.cli
|
|
11
|
+
|
|
12
|
+
Selection
|
|
13
|
+
---------
|
|
14
|
+
|
|
15
|
+
.. automodule:: unleap.selection
|
|
16
|
+
|
|
17
|
+
Extraction
|
|
18
|
+
----------
|
|
19
|
+
|
|
20
|
+
.. automodule:: unleap.extract
|
|
21
|
+
|
|
22
|
+
frcmod
|
|
23
|
+
------
|
|
24
|
+
|
|
25
|
+
.. automodule:: unleap.frcmod
|
|
26
|
+
|
|
27
|
+
.. autodata:: unleap.frcmod.LEAP_DEG_CORRECTION
|
|
28
|
+
:no-value:
|
|
29
|
+
|
|
30
|
+
OFF library
|
|
31
|
+
-----------
|
|
32
|
+
|
|
33
|
+
.. automodule:: unleap.lib
|
|
34
|
+
|
|
35
|
+
mol2 / SDF
|
|
36
|
+
----------
|
|
37
|
+
|
|
38
|
+
.. automodule:: unleap.mol2
|
|
39
|
+
|
|
40
|
+
Validation
|
|
41
|
+
----------
|
|
42
|
+
|
|
43
|
+
.. automodule:: unleap.validate
|
|
44
|
+
|
|
45
|
+
AmberTools helpers
|
|
46
|
+
------------------
|
|
47
|
+
|
|
48
|
+
.. automodule:: unleap.amber
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Sphinx configuration for unleap."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, os.path.abspath(".."))
|
|
8
|
+
|
|
9
|
+
project = "unleap"
|
|
10
|
+
author = "Zeke Piskulich"
|
|
11
|
+
copyright = "2026, Zeke Piskulich"
|
|
12
|
+
|
|
13
|
+
from unleap import __version__ as release # noqa: E402 (unleap/__init__.py has no heavy imports)
|
|
14
|
+
|
|
15
|
+
version = release
|
|
16
|
+
|
|
17
|
+
extensions = [
|
|
18
|
+
"sphinx.ext.autodoc",
|
|
19
|
+
"sphinx.ext.napoleon",
|
|
20
|
+
"sphinx.ext.viewcode",
|
|
21
|
+
"sphinx.ext.intersphinx",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
def _missing(module):
|
|
25
|
+
try:
|
|
26
|
+
importlib.import_module(module)
|
|
27
|
+
return False
|
|
28
|
+
except ImportError:
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Read the Docs (and any machine without AmberTools) has no ParmEd/RDKit; mock them for autodoc.
|
|
33
|
+
autodoc_mock_imports = [m for m in ("parmed", "rdkit", "numpy", "scipy") if _missing(m)]
|
|
34
|
+
autodoc_member_order = "bysource"
|
|
35
|
+
autodoc_default_options = {"members": True, "undoc-members": False, "show-inheritance": True}
|
|
36
|
+
napoleon_numpy_docstring = True
|
|
37
|
+
napoleon_google_docstring = False
|
|
38
|
+
|
|
39
|
+
intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
|
|
40
|
+
|
|
41
|
+
templates_path = []
|
|
42
|
+
exclude_patterns = ["_build"]
|
|
43
|
+
|
|
44
|
+
html_theme = "sphinx_rtd_theme"
|
|
45
|
+
html_theme_options = {"navigation_depth": 3, "collapse_navigation": False}
|
|
46
|
+
html_title = f"unleap {release}"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Amber and ParmEd behaviors
|
|
2
|
+
==========================
|
|
3
|
+
|
|
4
|
+
Each of these was found on real data and has a regression test (see :doc:`testing`).
|
|
5
|
+
Don't "simplify" these away.
|
|
6
|
+
|
|
7
|
+
1. Structure slicing breaks AmberParm
|
|
8
|
+
-------------------------------------
|
|
9
|
+
|
|
10
|
+
``struct[mask]`` keeps the full LJ tables, so the saved parm7 is malformed::
|
|
11
|
+
|
|
12
|
+
FLAG LENNARD_JONES_ACOEF has 66 elements; expected 45
|
|
13
|
+
|
|
14
|
+
``Structure.copy(AmberParm)`` drops ``parm_data`` entirely. Reload the file and call
|
|
15
|
+
``AmberParm.strip()``, which rebuilds the tables (:func:`unleap.extract.strip`).
|
|
16
|
+
|
|
17
|
+
2. ParmEd's frcmod writer loses precision
|
|
18
|
+
-----------------------------------------
|
|
19
|
+
|
|
20
|
+
``AmberParameterSet.write`` formats ``req``, ``theteq`` and masses with 3 decimals, but GAFF 2.2
|
|
21
|
+
uses 4-decimal bond lengths (1.3986). It also re-sorts improper atoms alphabetically.
|
|
22
|
+
:func:`unleap.frcmod.write` is our own writer in the same layout: 7 decimals for ``req``,
|
|
23
|
+
6 for ``theteq``, and impropers kept in topology order.
|
|
24
|
+
|
|
25
|
+
3. leap's DEGTORAD is 0.0174533
|
|
26
|
+
-------------------------------
|
|
27
|
+
|
|
28
|
+
``leap/src/leap/basics.h`` defines ``DEGTORAD 0.0174533``, not π/180. Every angle and phase tleap
|
|
29
|
+
writes is inflated by about 4.3e-7 when read back exactly, so 113.02 becomes 113.0200487.
|
|
30
|
+
Writing that back inflates it again. :func:`unleap.frcmod.full_params` divides by
|
|
31
|
+
:data:`unleap.frcmod.LEAP_DEG_CORRECTION`, but only when the corrected values are "rounder"
|
|
32
|
+
(``_leap_inflated``), so a topology written by another tool is not over-corrected.
|
|
33
|
+
|
|
34
|
+
4. tleap may store an improper reversed
|
|
35
|
+
---------------------------------------
|
|
36
|
+
|
|
37
|
+
The prmtop atom order can be ``l-k-j-i``, which puts the central atom second. It is the same
|
|
38
|
+
torsion angle, but "central = third atom" is then wrong. For one production ligand this turned the original
|
|
39
|
+
``cc-ns-c -o`` into ``o -c -ns-cc``, which tleap then never matched.
|
|
40
|
+
:func:`unleap.frcmod.improper_atoms` finds the central atom from bonding (bonded to the other
|
|
41
|
+
three) and reorders. The validation keys use the same function.
|
|
42
|
+
|
|
43
|
+
5. Wildcard and explicit impropers give different atom orders
|
|
44
|
+
-------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
If tleap matches ``X -X -c -o`` rather than an explicit ``cc-ns-c -o``, it stores the four atoms
|
|
47
|
+
in a different order. The improper angle, and so the energy, changes slightly even though the
|
|
48
|
+
parameters are identical. So the diff frcmod **always lists every improper explicitly**, as
|
|
49
|
+
parmchk2 does. :func:`unleap.frcmod.lookup_improper` only feeds the base score.
|
|
50
|
+
|
|
51
|
+
6. from_structure keys impropers under every permutation
|
|
52
|
+
--------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
``AmberParameterSet.from_structure`` stores each improper under every outer-atom permutation.
|
|
55
|
+
unleap dedupes on ``(central type, sorted outer types)``.
|
|
56
|
+
|
|
57
|
+
7. tleap requires MASS and NONBON together
|
|
58
|
+
------------------------------------------
|
|
59
|
+
|
|
60
|
+
A frcmod with NONBON but no MASS fails::
|
|
61
|
+
|
|
62
|
+
Modified force field files must have both MASS and NONBOND entries or neither.
|
|
63
|
+
|
|
64
|
+
The writer always emits both for every atom type in the set.
|
|
65
|
+
|
|
66
|
+
8. Coordinate reading
|
|
67
|
+
---------------------
|
|
68
|
+
|
|
69
|
+
ParmEd's format sniffing rejects ASCII restarts whose natom line also holds a temperature
|
|
70
|
+
(pmemd/REMD: ``52598 0.1400000E+05 0.2980000E+03``). :func:`unleap.extract.read_coordinates`
|
|
71
|
+
uses ``Rst7.open`` directly. For NetCDF it uses ``NetCDFTraj`` (a ``frame`` dimension exists) or
|
|
72
|
+
``NetCDFRestart``, and reads a single frame from ``_ncfile.variables``, so it never loads a 750 MB
|
|
73
|
+
trajectory just to take one frame.
|
|
74
|
+
|
|
75
|
+
9. The lib stores coordinates at about 1e-4 Å
|
|
76
|
+
---------------------------------------------
|
|
77
|
+
|
|
78
|
+
For stiff angles that alone moves the energy by about 1e-3 kcal/mol, so validation scores each
|
|
79
|
+
rebuilt topology on the *original* coordinates. The atom order is identical.
|
|
80
|
+
|
|
81
|
+
10. RDKit can't read GAFF-typed mol2
|
|
82
|
+
------------------------------------
|
|
83
|
+
|
|
84
|
+
RDKit's mol2 reader expects SYBYL types (``Element 'o' not found``). That's why the SDF is
|
|
85
|
+
written too.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Architecture
|
|
2
|
+
============
|
|
3
|
+
|
|
4
|
+
Layout
|
|
5
|
+
------
|
|
6
|
+
|
|
7
|
+
.. code-block:: text
|
|
8
|
+
|
|
9
|
+
unleap/
|
|
10
|
+
__init__.py version, UnleapError (user-facing failures -> exit code 2)
|
|
11
|
+
cli.py argparse + orchestration; writes report.txt
|
|
12
|
+
amber.py AMBERHOME helpers, base force-field presets, tleap / sander runners
|
|
13
|
+
selection.py --mask / --resname / auto-detect (standard residue name sets)
|
|
14
|
+
extract.py coordinate reading, ligand strip, sanity checks, PBC unwrap, HMR undo,
|
|
15
|
+
atom-type consistency
|
|
16
|
+
frcmod.py full parameter set, tleap-style base lookup, diff, base scoring, frcmod writer
|
|
17
|
+
lib.py OFF library via ParmEd ResidueTemplate
|
|
18
|
+
mol2.py Tripos mol2 + SDF writers, RDKit bond-order inference
|
|
19
|
+
validate.py tleap rebuilds, term-by-term comparison, sander energy comparison
|
|
20
|
+
tests/
|
|
21
|
+
conftest.py builds fixtures on the fly (RDKit -> antechamber -c gas -> parmchk2 -> tleap)
|
|
22
|
+
test_unleap.py
|
|
23
|
+
scripts/
|
|
24
|
+
vcm_sweep.py real-data regression sweep over ABFE vcm_* jobs
|
|
25
|
+
docs/ this documentation (Sphinx, Read the Docs theme)
|
|
26
|
+
|
|
27
|
+
Data flow
|
|
28
|
+
---------
|
|
29
|
+
|
|
30
|
+
:func:`unleap.cli.run` runs these steps in order:
|
|
31
|
+
|
|
32
|
+
1. :func:`unleap.extract.load` loads the topology with ParmEd and reads coordinates with
|
|
33
|
+
:func:`~unleap.extract.read_coordinates` (see :doc:`amber_notes`, item 8).
|
|
34
|
+
2. :func:`unleap.selection.ligand_mask`, then :func:`unleap.extract.strip`. This checks the
|
|
35
|
+
selection (exactly one whole residue, not covalent), warns about NBFIX/12-6-4/CMAP, then
|
|
36
|
+
**reloads** the topology and ``strip("!(mask)")``\ s it, then unwraps the ligand across PBC.
|
|
37
|
+
3. :func:`unleap.frcmod.full_params` builds the parameter set with
|
|
38
|
+
``AmberParameterSet.from_structure``, then applies the masses, improper dedupe and order,
|
|
39
|
+
and the leap angle correction.
|
|
40
|
+
4. :func:`unleap.cli.pick_base` scores each preset (or uses ``--base``) and returns the
|
|
41
|
+
:class:`~unleap.amber.BaseFF` and its loaded ``.dat``.
|
|
42
|
+
5. :func:`unleap.extract.undo_hmr` (needs the base for reference H masses), then
|
|
43
|
+
:func:`~unleap.extract.check_type_consistency`, then ``full_params`` again so the masses are final.
|
|
44
|
+
6. :func:`unleap.frcmod.diff_params` compares each term with the base using tleap's lookup rules
|
|
45
|
+
(:doc:`diff_rules`).
|
|
46
|
+
7. The frcmods, lib, mol2/sdf and ``tleap.in`` are written.
|
|
47
|
+
8. :func:`unleap.validate.run` saves ``LIG.orig.parm7``, does the three tleap rebuilds, then runs
|
|
48
|
+
:func:`~unleap.validate.compare_terms` and :func:`~unleap.validate.compare_energies`.
|
|
49
|
+
|
|
50
|
+
State between steps
|
|
51
|
+
-------------------
|
|
52
|
+
|
|
53
|
+
:class:`unleap.extract.Ligand` carries the state between steps. ``ligand.masses`` is the
|
|
54
|
+
per-atom mass list to write. It differs from ``lig.atoms[i].mass`` only when HMR was undone.
|
|
55
|
+
|
|
56
|
+
.. warning::
|
|
57
|
+
|
|
58
|
+
Never mutate parameter types attached to ``ligand.lig``. :func:`unleap.validate.save_stripped`
|
|
59
|
+
writes that structure as the reference everything is compared against. That's why
|
|
60
|
+
:func:`unleap.frcmod.full_params` replaces angle and dihedral types with corrected *copies*.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Diff frcmod matching rules
|
|
2
|
+
==========================
|
|
3
|
+
|
|
4
|
+
:func:`unleap.frcmod.compare_to_base` decides, term by term, whether the base force field already
|
|
5
|
+
reproduces the ligand's parameter. It mirrors tleap's lookup order:
|
|
6
|
+
|
|
7
|
+
.. list-table::
|
|
8
|
+
:header-rows: 1
|
|
9
|
+
:widths: 14 40 46
|
|
10
|
+
|
|
11
|
+
* - Term
|
|
12
|
+
- Lookup
|
|
13
|
+
- Equal if
|
|
14
|
+
* - atom type
|
|
15
|
+
- by name
|
|
16
|
+
- mass, rmin and ε within ``_TOL`` (rel/abs 1e-5)
|
|
17
|
+
* - bond / angle
|
|
18
|
+
- explicit, either direction
|
|
19
|
+
- k and eq within ``_TOL``
|
|
20
|
+
* - dihedral
|
|
21
|
+
- explicit (either direction), then ``X-b-c-X``
|
|
22
|
+
- same term list after dropping zero-k terms: periodicity, phase (mod 360), k, SCEE, SCNB
|
|
23
|
+
* - improper
|
|
24
|
+
- central third; outer atoms permuted; 0, then 1, then 2 ``X`` wildcards
|
|
25
|
+
- k, periodicity, phase (no 1-4 scaling)
|
|
26
|
+
|
|
27
|
+
A term that is missing from the base or differs is written explicitly. tleap gives explicit
|
|
28
|
+
frcmod entries loaded after the base priority over the base, which reproduces the original
|
|
29
|
+
build. Impropers are all written regardless (see :doc:`amber_notes`, item 5).
|
|
30
|
+
|
|
31
|
+
Base scoring
|
|
32
|
+
------------
|
|
33
|
+
|
|
34
|
+
:func:`unleap.frcmod.score_base` is the fraction of the ligand's bonded terms plus LJ types that
|
|
35
|
+
a base reproduces. Masses are ignored, because HMR may have altered them. ``--base auto`` takes
|
|
36
|
+
the best preset from :func:`unleap.amber.preset_bases`.
|
|
37
|
+
|
|
38
|
+
What the diff cannot express
|
|
39
|
+
----------------------------
|
|
40
|
+
|
|
41
|
+
A frcmod can add or override terms, but it cannot remove one. If the base would add a term the
|
|
42
|
+
original build didn't have, validation reports ``extra in rebuild``. ``LIG.full.frcmod`` does not
|
|
43
|
+
have this problem.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Extending
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
New base force fields
|
|
5
|
+
---------------------
|
|
6
|
+
|
|
7
|
+
Add them to ``_PRESETS`` in :mod:`unleap.amber`, as ``name -> (dat file, tleap preamble)``.
|
|
8
|
+
:func:`~unleap.amber.preset_bases` lists only the presets whose ``.dat`` exists in ``AMBERHOME``,
|
|
9
|
+
and ``--base auto`` scores them all.
|
|
10
|
+
|
|
11
|
+
Multi-residue or covalent ligands
|
|
12
|
+
---------------------------------
|
|
13
|
+
|
|
14
|
+
This would need:
|
|
15
|
+
|
|
16
|
+
* :func:`unleap.extract.strip` to accept several residues, and to stop rejecting bonds to
|
|
17
|
+
outside atoms.
|
|
18
|
+
* :func:`unleap.lib.write` to emit connected units (head/tail atoms).
|
|
19
|
+
* A policy for bonded terms that cross the cut.
|
|
20
|
+
|
|
21
|
+
NBFIX
|
|
22
|
+
-----
|
|
23
|
+
|
|
24
|
+
ParmEd can write ``LJEDIT`` sections, but ``AmberParm`` doesn't populate ``atom_type.nbfix`` on
|
|
25
|
+
load. You'd have to build the pairs from the A/B coefficient tables found in
|
|
26
|
+
``extract._nonbonded_checks``.
|
|
27
|
+
|
|
28
|
+
Conventions
|
|
29
|
+
-----------
|
|
30
|
+
|
|
31
|
+
* User-facing failures raise :class:`unleap.UnleapError`. The CLI turns them into exit code 2
|
|
32
|
+
with a one-line message.
|
|
33
|
+
* Warnings that still allow output go in ``ligand.warnings`` and appear as ``WARNING:`` lines in
|
|
34
|
+
``report.txt``.
|
|
35
|
+
* Any fix for a real topology needs a regression test that reproduces it synthetically.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Testing
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
Unit and integration tests
|
|
5
|
+
--------------------------
|
|
6
|
+
|
|
7
|
+
.. code-block:: bash
|
|
8
|
+
|
|
9
|
+
python -m pytest -q # ~5 s; skips everything if AmberTools isn't available
|
|
10
|
+
|
|
11
|
+
The fixtures are built per session in pytest's tmp dir (``tests/conftest.py``): acetaminophen
|
|
12
|
+
(GAFF2 and GAFF) and 4-chlorobenzoate next to an ff14SB peptide. Charges are antechamber
|
|
13
|
+
``-c gas`` for speed.
|
|
14
|
+
|
|
15
|
+
.. list-table::
|
|
16
|
+
:header-rows: 1
|
|
17
|
+
:widths: 45 55
|
|
18
|
+
|
|
19
|
+
* - Area
|
|
20
|
+
- Tests
|
|
21
|
+
* - Round trip and diff ⊆ parmchk2
|
|
22
|
+
- ``test_gaff2_roundtrip``
|
|
23
|
+
* - Custom dihedral/LJ edits recovered exactly
|
|
24
|
+
- ``test_custom_parameters_are_recovered``
|
|
25
|
+
* - Auto-detect in a complex, charged-ligand bond orders
|
|
26
|
+
- ``test_complex_autodetect_and_bond_orders``, ``test_sdf_is_rdkit_readable``
|
|
27
|
+
* - HMR undo / ``--keep-masses`` refusal
|
|
28
|
+
- ``test_hmr_is_undone``, ``test_keep_masses_rejects_split_types``
|
|
29
|
+
* - Base auto-detect (gaff1)
|
|
30
|
+
- ``test_gaff1_base_autodetect``
|
|
31
|
+
* - NetCDF trajectory and REMD-style rst7 input
|
|
32
|
+
- ``test_netcdf_trajectory_and_remd_rst7``
|
|
33
|
+
* - Reversed improper order, leap angle correction
|
|
34
|
+
- ``test_reversed_improper_order``, ``test_leap_angle_correction_recovers_input``
|
|
35
|
+
* - Error paths
|
|
36
|
+
- ``test_ambiguous_autodetect``, ``test_multi_residue_mask_rejected``, ``test_nbfix_warns``
|
|
37
|
+
* - Validation is not vacuous
|
|
38
|
+
- ``test_validation_catches_missing_terms`` (drops the dihedrals from the diff and expects FAIL)
|
|
39
|
+
|
|
40
|
+
When you fix a new real-world case, add a fixture that reproduces it. Items 3–5 in
|
|
41
|
+
:doc:`amber_notes` were invisible on the synthetic systems until real RBFE/ABFE topologies
|
|
42
|
+
exposed them.
|
|
43
|
+
|
|
44
|
+
Real-data sweep
|
|
45
|
+
---------------
|
|
46
|
+
|
|
47
|
+
.. code-block:: bash
|
|
48
|
+
|
|
49
|
+
python scripts/vcm_sweep.py path/to/ABFE /tmp/vcm_sweep # directory holding vcm_* jobs; ~10 min
|
|
50
|
+
|
|
51
|
+
``scripts/vcm_sweep.py`` runs unleap over every
|
|
52
|
+
``ABFE/vcm_*/results/.../t1_{complex,binder}_abfeproduction`` directory (its parm7 plus the λ=0
|
|
53
|
+
production rst7). It then rebuilds each job's ``inputs/abfe/<L>/binder_<L>.*`` with tleap and
|
|
54
|
+
compares that to the extracted ligand with :func:`~unleap.validate.compare_terms` and
|
|
55
|
+
:func:`~unleap.validate.compare_energies`.
|
|
56
|
+
|
|
57
|
+
That is the strongest end-to-end check available: it asks "is what we recovered what was put in?",
|
|
58
|
+
not just "do our files reproduce the topology?". It writes one directory per job and leg, plus
|
|
59
|
+
``summary.json``. It's worth re-running after any change to ``frcmod.py`` or ``validate.py``.
|
|
60
|
+
|
|
61
|
+
Last result: 52/52 topologies pass, and 46/46 with inputs are identical to their inputs.
|
|
62
|
+
|
|
63
|
+
Docs build
|
|
64
|
+
----------
|
|
65
|
+
|
|
66
|
+
.. code-block:: bash
|
|
67
|
+
|
|
68
|
+
make -C docs html SPHINXOPTS=-W # warnings are errors
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
unleap
|
|
2
|
+
======
|
|
3
|
+
|
|
4
|
+
**Get a ligand's parameter files back out of a built Amber system.**
|
|
5
|
+
|
|
6
|
+
tleap takes a ligand's ``mol2`` / ``frcmod`` / ``lib`` and builds a system (``parm7`` + ``rst7``).
|
|
7
|
+
``unleap`` goes the other way. Give it a complex or solvated system and it strips the ligand out,
|
|
8
|
+
regenerates those files, and then proves they are right. It rebuilds the ligand with tleap and
|
|
9
|
+
checks every parameter and a sander energy breakdown against the original topology.
|
|
10
|
+
|
|
11
|
+
.. code-block:: bash
|
|
12
|
+
|
|
13
|
+
unleap -p complex.parm7 -c complex.rst7 -o lig_out
|
|
14
|
+
|
|
15
|
+
Typical uses:
|
|
16
|
+
|
|
17
|
+
* The original ligand inputs are gone, or you no longer trust they match what was simulated.
|
|
18
|
+
* You want the exact parameters used in a production run (RBFE/ABFE/VCM job) to reuse elsewhere.
|
|
19
|
+
* You want to check that a run's topology really used the ligand files you think it did.
|
|
20
|
+
|
|
21
|
+
.. toctree::
|
|
22
|
+
:maxdepth: 2
|
|
23
|
+
:caption: User guide
|
|
24
|
+
|
|
25
|
+
user/installation
|
|
26
|
+
user/quickstart
|
|
27
|
+
user/options
|
|
28
|
+
user/outputs
|
|
29
|
+
user/validation
|
|
30
|
+
user/limitations
|
|
31
|
+
|
|
32
|
+
.. toctree::
|
|
33
|
+
:maxdepth: 2
|
|
34
|
+
:caption: Developer guide
|
|
35
|
+
|
|
36
|
+
dev/architecture
|
|
37
|
+
dev/amber_notes
|
|
38
|
+
dev/diff_rules
|
|
39
|
+
dev/testing
|
|
40
|
+
dev/extending
|
|
41
|
+
|
|
42
|
+
.. toctree::
|
|
43
|
+
:maxdepth: 2
|
|
44
|
+
:caption: Reference
|
|
45
|
+
|
|
46
|
+
api
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Installation
|
|
2
|
+
============
|
|
3
|
+
|
|
4
|
+
Requirements
|
|
5
|
+
------------
|
|
6
|
+
|
|
7
|
+
* AmberTools: ``tleap`` and ``sander``, plus ``$AMBERHOME/dat/leap/parm/gaff*.dat``
|
|
8
|
+
* Python 3.9+ with `ParmEd <https://parmed.github.io/ParmEd/>`_, `RDKit <https://www.rdkit.org/>`_,
|
|
9
|
+
NumPy and SciPy
|
|
10
|
+
|
|
11
|
+
Install
|
|
12
|
+
-------
|
|
13
|
+
|
|
14
|
+
The easiest way to get everything is conda-forge, whose ``ambertools`` package includes ParmEd:
|
|
15
|
+
|
|
16
|
+
.. code-block:: bash
|
|
17
|
+
|
|
18
|
+
conda create -n unleap -c conda-forge ambertools rdkit
|
|
19
|
+
conda activate unleap
|
|
20
|
+
pip install unleap
|
|
21
|
+
|
|
22
|
+
If you already have an AmberTools install, source it (``source $AMBERHOME/amber.sh``) and
|
|
23
|
+
``pip install unleap`` into that Python.
|
|
24
|
+
|
|
25
|
+
From a checkout, for development:
|
|
26
|
+
|
|
27
|
+
.. code-block:: bash
|
|
28
|
+
|
|
29
|
+
git clone https://github.com/piskuliche/unleap.git
|
|
30
|
+
cd unleap
|
|
31
|
+
pip install -e ".[test,docs]"
|
|
32
|
+
|
|
33
|
+
``AMBERHOME`` must be set. If it isn't, unleap stops with ``AMBERHOME is not set``.
|
|
34
|
+
|
|
35
|
+
Building these docs
|
|
36
|
+
-------------------
|
|
37
|
+
|
|
38
|
+
.. code-block:: bash
|
|
39
|
+
|
|
40
|
+
pip install -r docs/requirements.txt
|
|
41
|
+
make -C docs html # output in docs/_build/html
|