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.
Files changed (39) hide show
  1. unleap-0.1.0/LICENSE +21 -0
  2. unleap-0.1.0/MANIFEST.in +4 -0
  3. unleap-0.1.0/PKG-INFO +84 -0
  4. unleap-0.1.0/README.md +53 -0
  5. unleap-0.1.0/docs/Makefile +13 -0
  6. unleap-0.1.0/docs/api.rst +48 -0
  7. unleap-0.1.0/docs/conf.py +46 -0
  8. unleap-0.1.0/docs/dev/amber_notes.rst +85 -0
  9. unleap-0.1.0/docs/dev/architecture.rst +60 -0
  10. unleap-0.1.0/docs/dev/diff_rules.rst +43 -0
  11. unleap-0.1.0/docs/dev/extending.rst +35 -0
  12. unleap-0.1.0/docs/dev/testing.rst +68 -0
  13. unleap-0.1.0/docs/index.rst +46 -0
  14. unleap-0.1.0/docs/requirements.txt +2 -0
  15. unleap-0.1.0/docs/user/installation.rst +41 -0
  16. unleap-0.1.0/docs/user/limitations.rst +44 -0
  17. unleap-0.1.0/docs/user/options.rst +95 -0
  18. unleap-0.1.0/docs/user/outputs.rst +44 -0
  19. unleap-0.1.0/docs/user/quickstart.rst +71 -0
  20. unleap-0.1.0/docs/user/validation.rst +49 -0
  21. unleap-0.1.0/pyproject.toml +47 -0
  22. unleap-0.1.0/setup.cfg +4 -0
  23. unleap-0.1.0/tests/conftest.py +72 -0
  24. unleap-0.1.0/tests/test_unleap.py +222 -0
  25. unleap-0.1.0/unleap/__init__.py +7 -0
  26. unleap-0.1.0/unleap/amber.py +125 -0
  27. unleap-0.1.0/unleap/cli.py +126 -0
  28. unleap-0.1.0/unleap/extract.py +215 -0
  29. unleap-0.1.0/unleap/frcmod.py +250 -0
  30. unleap-0.1.0/unleap/lib.py +11 -0
  31. unleap-0.1.0/unleap/mol2.py +66 -0
  32. unleap-0.1.0/unleap/selection.py +57 -0
  33. unleap-0.1.0/unleap/validate.py +126 -0
  34. unleap-0.1.0/unleap.egg-info/PKG-INFO +84 -0
  35. unleap-0.1.0/unleap.egg-info/SOURCES.txt +37 -0
  36. unleap-0.1.0/unleap.egg-info/dependency_links.txt +1 -0
  37. unleap-0.1.0/unleap.egg-info/entry_points.txt +2 -0
  38. unleap-0.1.0/unleap.egg-info/requires.txt +11 -0
  39. 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.
@@ -0,0 +1,4 @@
1
+ include LICENSE README.md
2
+ recursive-include tests *.py
3
+ recursive-include docs *.rst *.py *.txt Makefile
4
+ prune docs/_build
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
+ [![tests](https://github.com/piskuliche/unleap/actions/workflows/tests.yml/badge.svg)](https://github.com/piskuliche/unleap/actions/workflows/tests.yml)
35
+ [![docs](https://github.com/piskuliche/unleap/actions/workflows/docs.yml/badge.svg)](https://piskuliche.github.io/unleap/)
36
+ [![PyPI](https://img.shields.io/pypi/v/unleap.svg)](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
+ [![tests](https://github.com/piskuliche/unleap/actions/workflows/tests.yml/badge.svg)](https://github.com/piskuliche/unleap/actions/workflows/tests.yml)
4
+ [![docs](https://github.com/piskuliche/unleap/actions/workflows/docs.yml/badge.svg)](https://piskuliche.github.io/unleap/)
5
+ [![PyPI](https://img.shields.io/pypi/v/unleap.svg)](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,2 @@
1
+ sphinx>=7
2
+ sphinx-rtd-theme>=2
@@ -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