caddforge-fep 0.7.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 (32) hide show
  1. caddforge_fep-0.7.0/.github/workflows/release.yml +95 -0
  2. caddforge_fep-0.7.0/.gitignore +25 -0
  3. caddforge_fep-0.7.0/LICENSE +21 -0
  4. caddforge_fep-0.7.0/PKG-INFO +455 -0
  5. caddforge_fep-0.7.0/README.md +438 -0
  6. caddforge_fep-0.7.0/pyproject.toml +43 -0
  7. caddforge_fep-0.7.0/scripts/check_release.py +98 -0
  8. caddforge_fep-0.7.0/scripts/ci_tests.json +8 -0
  9. caddforge_fep-0.7.0/scripts/run_ci_tests.py +84 -0
  10. caddforge_fep-0.7.0/src/cadd_fep/__init__.py +5 -0
  11. caddforge_fep-0.7.0/src/cadd_fep/gui_extension.py +1797 -0
  12. caddforge_fep-0.7.0/src/cadd_fep/gui_static/common.js +53 -0
  13. caddforge_fep-0.7.0/src/cadd_fep/gui_static/fep.js +12 -0
  14. caddforge_fep-0.7.0/src/cadd_fep/gui_static/openfe-abfe-panel.js +766 -0
  15. caddforge_fep-0.7.0/src/cadd_fep/gui_static/openfe-rbfe-septop-panel.js +595 -0
  16. caddforge_fep-0.7.0/src/cadd_fep/gui_static/rbfe-network.js +1265 -0
  17. caddforge_fep-0.7.0/src/cadd_fep/openfe_abfe/__init__.py +5 -0
  18. caddforge_fep-0.7.0/src/cadd_fep/openfe_abfe/defaults.py +118 -0
  19. caddforge_fep-0.7.0/src/cadd_fep/openfe_abfe/workflow.py +634 -0
  20. caddforge_fep-0.7.0/src/cadd_fep/openfe_rbfe_septop/__init__.py +5 -0
  21. caddforge_fep-0.7.0/src/cadd_fep/openfe_rbfe_septop/workflow.py +1672 -0
  22. caddforge_fep-0.7.0/src/cadd_fep/rbfe/__init__.py +29 -0
  23. caddforge_fep-0.7.0/src/cadd_fep/rbfe/bundle.py +200 -0
  24. caddforge_fep-0.7.0/src/cadd_fep/rbfe/network.py +875 -0
  25. caddforge_fep-0.7.0/src/cadd_fep/tool.py +68 -0
  26. caddforge_fep-0.7.0/tests/test_gui_extension.py +371 -0
  27. caddforge_fep-0.7.0/tests/test_openfe_abfe_defaults.py +230 -0
  28. caddforge_fep-0.7.0/tests/test_openfe_abfe_workflow.py +431 -0
  29. caddforge_fep-0.7.0/tests/test_openfe_rbfe_septop_workflow.py +806 -0
  30. caddforge_fep-0.7.0/tests/test_placeholder_fep.py +2 -0
  31. caddforge_fep-0.7.0/tests/test_rbfe_network_planner.py +217 -0
  32. caddforge_fep-0.7.0/tests/test_release_metadata.py +77 -0
@@ -0,0 +1,95 @@
1
+ name: CI and PyPI release
2
+ 'on':
3
+ push:
4
+ branches:
5
+ - '**'
6
+ tags:
7
+ - v*
8
+ pull_request: null
9
+ workflow_dispatch: null
10
+ permissions:
11
+ contents: read
12
+ jobs:
13
+ build:
14
+ name: Build and validate distributions
15
+ runs-on: ubuntu-24.04
16
+ steps:
17
+ - uses: actions/checkout@v7.0.1
18
+ with:
19
+ fetch-depth: 0
20
+ persist-credentials: false
21
+ - uses: actions/setup-python@v7.0.0
22
+ with:
23
+ python-version: '3.12'
24
+ - name: Install build tools
25
+ run: python -m pip --isolated install --index-url https://pypi.org/simple --upgrade build twine packaging
26
+ - name: Build wheel from source distribution
27
+ run: python -m build
28
+ - name: Validate package metadata
29
+ run: python -m twine check --strict dist/*
30
+ - name: Check distribution identity and release version
31
+ env:
32
+ IS_RELEASE: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
33
+ RELEASE_TAG: ${{ github.ref_name }}
34
+ run: python scripts/check_release.py .
35
+ - uses: actions/upload-artifact@v7.0.2
36
+ with:
37
+ name: distributions
38
+ path: dist/*
39
+ if-no-files-found: error
40
+ test:
41
+ name: Test wheel (Python ${{ matrix.python }})
42
+ needs: build
43
+ runs-on: ubuntu-24.04
44
+ strategy:
45
+ fail-fast: false
46
+ matrix:
47
+ python:
48
+ - '3.10'
49
+ - '3.12'
50
+ steps:
51
+ - uses: actions/checkout@v7.0.1
52
+ with:
53
+ persist-credentials: false
54
+ - uses: actions/setup-python@v7.0.0
55
+ with:
56
+ python-version: ${{ matrix.python }}
57
+ - uses: actions/download-artifact@v8.0.1
58
+ with:
59
+ name: distributions
60
+ path: dist
61
+ - name: Install built wheel and test dependencies
62
+ shell: bash
63
+ run: |-
64
+ wheels=(dist/*.whl)
65
+ python -m pip --isolated install --index-url https://pypi.org/simple "${wheels[0]}[gui]"
66
+ python -m pip --isolated install --index-url https://pypi.org/simple pytest packaging 'tomli; python_version < "3.11"' 'httpx'
67
+ - name: Test installed wheel outside the checkout
68
+ env:
69
+ MPLBACKEND: Agg
70
+ OMP_NUM_THREADS: '1'
71
+ OPENBLAS_NUM_THREADS: '1'
72
+ run: python scripts/run_ci_tests.py
73
+ - name: Check installed dependencies
74
+ run: python -m pip check
75
+ publish:
76
+ name: Publish to PyPI
77
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
78
+ needs:
79
+ - build
80
+ - test
81
+ runs-on: ubuntu-24.04
82
+ environment:
83
+ name: pypi
84
+ url: https://pypi.org/project/caddforge-fep/
85
+ permissions:
86
+ id-token: write
87
+ concurrency:
88
+ group: pypi-${{ github.ref }}
89
+ cancel-in-progress: false
90
+ steps:
91
+ - uses: actions/download-artifact@v8.0.1
92
+ with:
93
+ name: distributions
94
+ path: dist
95
+ - uses: pypa/gh-action-pypi-publish@v1.14.2
@@ -0,0 +1,25 @@
1
+ # Byte-compiled / cache
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.so
5
+
6
+ # Environments
7
+ .venv/
8
+ .env
9
+
10
+ # Packaging / build
11
+ build/
12
+ dist/
13
+ *.egg-info/
14
+ .hatch/
15
+
16
+ # Tooling caches
17
+ .pytest_cache/
18
+ .ruff_cache/
19
+ .mypy_cache/
20
+ .coverage
21
+ runs/
22
+
23
+ # IDE/editor
24
+ .idea/
25
+ .vscode/
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 The CADDForge Authors
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,455 @@
1
+ Metadata-Version: 2.5
2
+ Name: caddforge-fep
3
+ Version: 0.7.0
4
+ Summary: Free energy perturbation workflows for the CADDForge toolbox.
5
+ Author-email: Manuel Sellner <manuel.sellner@idorsia.com>, Christoph Sager <christoph.sager@idorsia.com>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: caddforge-core>=0.21.0
10
+ Requires-Dist: pdbfixer>=1.9
11
+ Requires-Dist: rdkit>=2024.03.1
12
+ Provides-Extra: gui
13
+ Requires-Dist: caddforge-gui>=0.2.0; extra == 'gui'
14
+ Requires-Dist: fastapi>=0.110; extra == 'gui'
15
+ Requires-Dist: python-multipart>=0.0.21; extra == 'gui'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # CADDForge FEP Plugin
19
+
20
+ Install this plugin with `python -m pip install caddforge-fep`. It depends directly
21
+ on `caddforge-core>=0.21.0`; Python imports and CLI commands still use `caddforge`.
22
+ For the complete public toolbox, including user-facing extras, install
23
+ `python -m pip install caddforge`. When migrating an existing environment, uninstall
24
+ the former `caddforge` core distribution before installing renamed core and updated plugins.
25
+
26
+ `caddforge-fep` provides free energy perturbation workflows as plugins for the CADDForge framework.
27
+
28
+ ## Optional GUI dependencies
29
+ The core FEP workflows can be installed without GUI dependencies.
30
+
31
+ If you want to use the FEP GUI extension, install the optional GUI extra:
32
+ ```bash
33
+ pip install "caddforge-fep[gui]"
34
+ ```
35
+
36
+ ## Installation notes
37
+ - It is recommended to install `openfe` and the rest of the scientific runtime stack with `conda`.
38
+ - To avoid environment conflicts, install all required `conda` packages first, and only then install CADDForge packages with `pip`.
39
+
40
+ Typical order:
41
+ ```bash
42
+ conda create -n caddforge-fep python=3.10
43
+ conda activate caddforge-fep
44
+ conda install -c conda-forge openfe openff-toolkit rdkit pdbfixer
45
+ pip install caddforge-core caddforge-fep
46
+ ```
47
+
48
+ Notes:
49
+ - The current GUI extension covers both `openfe-abfe` and `openfe-rbfe-septop`.
50
+ - The `openfe-rbfe-septop` GUI includes an interactive ligand-network canvas with draggable nodes, manual edge creation/removal, edge rescoring, and network save/load support.
51
+
52
+ ## Implemented Tools
53
+ - [`openfe-abfe`](#tool-openfe-abfe)
54
+ - [`openfe-rbfe-septop`](#tool-openfe-rbfe-septop)
55
+
56
+ ## Tool: `openfe-abfe`
57
+
58
+ ### What it does
59
+ The `openfe-abfe` tool prepares, and optionally runs, OpenFE absolute binding free energy calculations through the standard CADDForge workflow/job interface. It supports:
60
+ - ligand input from a multi-record SDF with ligand selection by index
61
+ - protein input from PDB
62
+ - optional protein cleanup with `pdbfixer`
63
+ - OpenFE settings overrides through dot-path config entries
64
+ - local execution or Slurm execution with scratch work under `SLURM_TMPDIR`
65
+ - standardized CADDForge outputs (`result_record.json`, transformation JSON, OpenFE logs)
66
+
67
+ ### External dependencies
68
+ These tools and libraries are required at runtime and are not installed automatically by this Python package:
69
+ - `openfe`
70
+ - `openff`
71
+ - `rdkit`
72
+ - `pdbfixer`
73
+
74
+ Installation note:
75
+ - `openfe` is best installed with `conda`.
76
+ - Install the `conda` packages first, then install `caddforge-core` / `caddforge-fep` with `pip`.
77
+
78
+ ### CLI usage
79
+ ```bash
80
+ caddforge run fep openfe-abfe --config abfe.yaml
81
+ ```
82
+
83
+ ### Python API usage
84
+ ```python
85
+ from caddforge.core.execution import load_result_for_job, submit_workflow
86
+ from caddforge.plugins import get_tool
87
+
88
+ spec = get_tool("fep", "openfe-abfe")
89
+ cfg = spec.config_cls(
90
+ ligand_sdf="ligand_neutral.sdf",
91
+ protein_pdb="protein_fixed.pdb",
92
+ ligand_index=0,
93
+ backend="local", # or "slurm"
94
+ run_quickrun=True,
95
+ )
96
+
97
+ workflow = spec.workflow_cls(cfg)
98
+ job = submit_workflow(workflow, cfg, backend_name=cfg.backend)
99
+ result = load_result_for_job(job)
100
+ if result is not None:
101
+ print(result.status.value, result.metrics)
102
+ ```
103
+
104
+ ### Example config
105
+ ```yaml
106
+ ligand_sdf: ligand_neutral.sdf
107
+ protein_pdb: protein_fixed.pdb
108
+ ligand_index: 0
109
+ fix_protein_pdb: false
110
+ partial_charge_method: am1bcc
111
+ off_toolkit_backend: ambertools
112
+ host_min_distance_nm: 0.5
113
+ host_max_distance_nm: 1.5
114
+ protocol_repeats: 1
115
+ settings_overrides:
116
+ complex_simulation_settings.real_time_analysis_interval: 10 picosecond
117
+ complex_equil_simulation_settings.equilibration_length: 0.2 nanosecond
118
+ complex_equil_simulation_settings.production_length: 0.5 nanosecond
119
+ complex_equil_simulation_settings.equilibration_length_nvt: 0.10 nanosecond
120
+ complex_simulation_settings.equilibration_length: 0.2 nanosecond
121
+ complex_simulation_settings.production_length: 2.0 nanosecond
122
+ complex_simulation_settings.n_replicas: 68
123
+ solvent_equil_simulation_settings.equilibration_length: 0.1 nanosecond
124
+ solvent_equil_simulation_settings.production_length: 0.5 nanosecond
125
+ solvent_equil_simulation_settings.equilibration_length_nvt: 0.05 nanosecond
126
+ solvent_simulation_settings.equilibration_length: 0.1 nanosecond
127
+ solvent_simulation_settings.production_length: 1 nanosecond
128
+ solvent_simulation_settings.real_time_analysis_interval: 10 picosecond
129
+ solvent_simulation_settings.n_replicas: 60
130
+ engine_settings.compute_platform: CUDA
131
+ complex_lambda_settings.lambda_elec:
132
+ [0, 0.05, 0.1, 0.14, 0.19, 0.24, 0.29, 0.33, 0.38, 0.43, 0.48, 0.52, 0.57, 0.62, 0.67, 0.71,
133
+ 0.76, 0.81, 0.86, 0.9, 0.95, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
134
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
135
+ complex_lambda_settings.lambda_vdw:
136
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.06, 0.12, 0.18, 0.24,
137
+ 0.28, 0.32, 0.36, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.62, 0.64, 0.66, 0.68, 0.69, 0.7, 0.71,
138
+ 0.72, 0.73, 0.74, 0.75, 0.76, 0.77, 0.78, 0.79, 0.8, 0.81, 0.82, 0.83, 0.83, 0.84, 0.85,
139
+ 0.86, 0.87, 0.88, 0.89, 0.9, 0.91, 0.92, 0.94, 0.96, 0.98, 1]
140
+ complex_lambda_settings.lambda_restraints:
141
+ [0, 0, 0, 0.01, 0.01, 0.02, 0.03, 0.04, 0.06, 0.07, 0.09, 0.11, 0.13, 0.16, 0.18, 0.21, 0.24,
142
+ 0.27, 0.3, 0.33, 0.37, 0.4, 0.44, 0.49, 0.53, 0.57, 0.62, 0.67, 0.72, 0.77, 0.83, 0.88,
143
+ 0.94, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
144
+ 1, 1, 1, 1, 1, 1, 1]
145
+ solvent_lambda_settings.lambda_elec:
146
+ [0, 0.01, 0.02, 0.03, 0.04, 0.07, 0.09, 0.11, 0.13, 0.15, 0.17, 0.22, 0.26, 0.3, 0.35, 0.39,
147
+ 0.43, 0.48, 0.52, 0.57, 0.61, 0.65, 0.7, 0.74, 0.78, 0.83, 0.87, 0.91, 0.96, 1, 1, 1, 1,
148
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
149
+ solvent_lambda_settings.lambda_vdw:
150
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
151
+ 0.1, 0.18, 0.26, 0.32, 0.38, 0.42, 0.46, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68, 0.71,
152
+ 0.74, 0.76, 0.78, 0.8, 0.81, 0.83, 0.84, 0.86, 0.88, 0.9, 0.92, 0.94, 0.96, 0.98, 1]
153
+ solvent_lambda_settings.lambda_restraints:
154
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
155
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
156
+ run_quickrun: true
157
+ quickrun_extra_args: []
158
+ ```
159
+
160
+ ### Tool-specific config options
161
+
162
+ #### Required / primary inputs
163
+ - `ligand_sdf` (`Path`): SDF containing the ligand set.
164
+ - `protein_pdb` (`Path`): Protein receptor PDB.
165
+ - `ligand_index` (`int`, default `0`): SDF record index to use for the ABFE calculation.
166
+
167
+ #### Optional preparation options
168
+ - `fix_protein_pdb` (`bool`, default `false`): If `true`, run `pdbfixer` before OpenFE setup.
169
+ - `partial_charge_method` (`str`, default `"am1bcc"`): Partial charge method for OpenFF charge generation.
170
+ - `off_toolkit_backend` (`str`, default `"ambertools"`): OpenFF backend used for charge assignment.
171
+ - `charge_n_conformers` (`int`, default `1`): Number of conformers for charge generation.
172
+ - `charge_nagl_model` (`str | None`): Optional NAGL model name.
173
+ - `charge_processors` (`int`, default `1`): Number of processors for charge assignment.
174
+ - `overwrite_charges` (`bool`, default `false`): Overwrite existing molecule charges.
175
+ - `require_neutral_ligand` (`bool`, default `true`): Enforce the OpenFE ABFE neutral-ligand limitation.
176
+
177
+ #### Protocol options
178
+ - `host_min_distance_nm` (`float`, default `0.5`): Minimum Boresch restraint host distance in nm.
179
+ - `host_max_distance_nm` (`float`, default `1.5`): Maximum Boresch restraint host distance in nm.
180
+ - `protocol_repeats` (`int | None`, default `1`): Number of independent protocol repeats per leg. Explicit `null` retains OpenFE's repeat default.
181
+ - `settings_overrides` (`dict[str, Any]`): The ABFE defaults below, merged with explicit dot-path overrides. Omitted, empty, or partial dictionaries retain all unspecified defaults.
182
+
183
+ | Settings group | NVT equilibration | Equilibration | Production | Lambda replicas | Analysis interval |
184
+ | --- | --- | --- | --- | --- | --- |
185
+ | Complex pre-equilibration | 0.10 ns | 0.2 ns | 0.5 ns | — | — |
186
+ | Complex alchemical simulation | — | 0.2 ns | 2.0 ns | 68 | 10 ps |
187
+ | Solvent pre-equilibration | 0.05 ns | 0.1 ns | 0.5 ns | — | — |
188
+ | Solvent alchemical simulation | — | 0.1 ns | 1 ns | 60 | 10 ps |
189
+
190
+ The default compute platform is `CUDA`. Each complex lambda array has 68 entries;
191
+ each solvent lambda array has 60 entries, with solvent restraints zero throughout.
192
+ `n_replicas` counts lambda replicas within a leg; it is distinct from independent
193
+ `protocol_repeats`. Keep each leg's three lambda-array lengths consistent with its
194
+ replica count when overriding the schedules.
195
+
196
+ The CLI, Python API, and GUI use the same defaults. Explicit
197
+ `settings_overrides.protocol_repeats` takes precedence over the top-level
198
+ `protocol_repeats`. Generated examples specify repeats only at the top level.
199
+
200
+ `settings_overrides` accepts plain values or strings of the form `<number> <unit>`, for example:
201
+ - `5 nanosecond`
202
+ - `0.15 molar`
203
+
204
+ #### Execution / output options
205
+ - `transformation_dir_name` (`str`, default `"abfe_json"`): Directory name used for OpenFE transformation JSON files.
206
+ - `run_quickrun` (`bool`, default `true`): If `true`, execute `openfe quickrun`.
207
+ - `quickrun_workdir` (`Path | None`): Optional OpenFE working directory.
208
+ - `quickrun_output` (`Path | None`): Optional output JSON path for `openfe quickrun`.
209
+ - `quickrun_extra_args` (`list[str]`, default `[]`): Extra raw arguments appended to `openfe quickrun`.
210
+
211
+ ### Outputs
212
+ For each run, artifacts are written under `runs/<date>/<job-id>/`:
213
+ - `<transformation_dir_name>/`: OpenFE transformation JSON
214
+ - `results/openfe_results.json`: OpenFE quickrun output JSON
215
+ - `results/openfe_stdout.log`: OpenFE stdout
216
+ - `results/openfe_stderr.log`: OpenFE stderr
217
+ - `results/result_record.json`: standardized CADDForge `Result`
218
+ - `openfe_run/`: OpenFE working directory
219
+ - `stdout.log` / `stderr.log`: CADDForge workflow logs
220
+
221
+ ## Tool: `openfe-rbfe-septop`
222
+
223
+ ### What it does
224
+ The `openfe-rbfe-septop` tool plans, prepares, and optionally runs separate-topology RBFE calculations with OpenFE SepTop. It supports:
225
+ - input as one shared protein plus many ligand poses, or as many aligned protein-plus-ligand input pairs
226
+ - explicit project-compound selection via config
227
+ - automatic ligand-network generation with:
228
+ - charge bucketing
229
+ - compatibility grouping based on similarity and protein-clash filtering
230
+ - project-centered radial maps
231
+ - local orphan-cluster hubs
232
+ - optional diagnostic cycle edges
233
+ - extension of an existing persisted ligand network without removing existing edges
234
+ - portable self-contained network bundles with bundled proteins plus reusable completed edge records
235
+ - bundle refresh mode to rebuild a fresh portable bundle from an existing finished campaign
236
+ - persisted ligand-network JSON plus GraphML export
237
+ - local sequential execution or Slurm fan-out submission with one child job per transformation edge
238
+ - an interactive GUI workflow with network visualization and manual editing
239
+
240
+ ### External dependencies
241
+ These tools and libraries are required at runtime and are not installed automatically by this Python package:
242
+ - `openfe`
243
+ - `openff`
244
+ - `rdkit`
245
+
246
+ Notes:
247
+ - This workflow assumes ligand poses are already meaningful and aligned in the protein frame.
248
+ - For Slurm, the campaign job plans/prepares the network once and then submits one child job per transformation so edges can run independently in parallel.
249
+ - `openfe` is best installed with `conda`, and all `conda` dependencies should be installed before adding the CADDForge packages with `pip`.
250
+
251
+ ### CLI usage
252
+ ```bash
253
+ caddforge run fep openfe-rbfe-septop --config rbfe_septop.yaml
254
+ ```
255
+
256
+ Refresh a portable bundle from an existing finished campaign:
257
+ ```bash
258
+ caddforge run fep openfe-rbfe-septop --config rbfe_refresh_bundle.yaml
259
+ ```
260
+
261
+ ### Python API usage
262
+ ```python
263
+ from caddforge.core.execution import load_result_for_job, submit_workflow
264
+ from caddforge.plugins import get_tool
265
+
266
+ spec = get_tool("fep", "openfe-rbfe-septop")
267
+ cfg = spec.config_cls(
268
+ ligand_sdf="ligands.sdf",
269
+ ligand_sdfs=[],
270
+ protein_pdb="protein.pdb",
271
+ project_compound_ids=["proj_anchor_01", "proj_anchor_02"],
272
+ operation_mode="run",
273
+ backend="local", # or "slurm"
274
+ protocol_repeats=3,
275
+ )
276
+
277
+ workflow = spec.workflow_cls(cfg)
278
+ job = submit_workflow(workflow, cfg, backend_name=cfg.backend)
279
+ result = load_result_for_job(job)
280
+ if result is not None:
281
+ print(result.status.value, result.metrics)
282
+ ```
283
+
284
+ ### Example config
285
+ ```yaml
286
+ operation_mode: run
287
+ ligand_sdf: ligands.sdf
288
+ ligand_sdfs: []
289
+ protein_pdb: protein.pdb
290
+ complexes: []
291
+ project_compound_ids:
292
+ - proj_anchor_01
293
+ - proj_anchor_02
294
+ bundle_refresh_source_path: null
295
+ existing_network_path: null
296
+ existing_network_bundle_path: null
297
+ existing_transformations_manifest_path: null
298
+ export_network_bundle: true
299
+ reuse_completed_edges: true
300
+
301
+ partial_charge_method: am1bcc
302
+ off_toolkit_backend: ambertools
303
+ protocol_repeats: 3
304
+ settings_overrides:
305
+ engine_settings.compute_platform: CUDA
306
+
307
+ compatibility_threshold: 0.45
308
+ project_assignment_threshold: 0.35
309
+ local_cluster_threshold: 0.55
310
+ max_clash_score: 0.35
311
+ diagnostic_cycle_edges_per_component: 1
312
+
313
+ run_quickrun: true
314
+ quickrun_extra_args: []
315
+ ```
316
+
317
+ ### Tool-specific config options
318
+
319
+ #### Required / primary inputs
320
+ Use either:
321
+ - `ligand_sdf` and/or `ligand_sdfs`, together with `protein_pdb`
322
+
323
+ Or:
324
+ - `complexes`
325
+
326
+ Primary fields:
327
+ - `operation_mode` (`"plan" | "extend" | "prepare" | "run" | "refresh_bundle"`, default `"run"`): Network-only planning, network extension, transformation preparation, full execution, or rebuilding a portable bundle from an existing finished campaign/source.
328
+ - `ligand_sdf` (`Path | None`): Optional SDF containing ligands already placed in the protein frame.
329
+ - `ligand_sdfs` (`list[Path]`, default `[]`): Optional list of SDF files used with the shared protein. Each file may contain one or many ligands.
330
+ - `protein_pdb` (`Path | None`): Shared aligned protein PDB used with `ligand_sdf` and/or `ligand_sdfs`.
331
+ - `complexes` (`list[SeptopComplexInput]`, default `[]`): Alternative aligned per-ligand input pairs, each containing a `protein_pdb` and a `ligand_sdf`.
332
+ - `project_compound_ids` (`list[str]`, default `[]`): Explicit ligand identifiers used as project anchors.
333
+ - `bundle_refresh_source_path` (`Path | None`): Required for `operation_mode: refresh_bundle`. Accepts a campaign directory, a `results/` directory, a `ligand_network.json`, or an existing bundle zip.
334
+ - `existing_network_path` (`Path | None`): Persisted network JSON used for `extend`, `prepare`, or `run` from an existing network.
335
+ - `existing_network_bundle_path` (`Path | None`): Portable self-contained RBFE bundle zip used for `extend`, `prepare`, or `run`.
336
+ - `existing_transformations_manifest_path` (`Path | None`): Existing `transformations_manifest.json` used to reuse already prepared transformation JSON files without regenerating them.
337
+
338
+ `SeptopComplexInput` fields:
339
+ - `ligand_sdf` (`Path`): Ligand SDF file.
340
+ - `protein_pdb` (`Path`): Protein PDB file for that ligand pose.
341
+ - `ligand_index` (`int`, default `0`): Ligand record to use from the SDF.
342
+ - `ligand_id` (`str | None`): Reserved identifier field; current implementation derives IDs from ligand names/properties.
343
+
344
+ Current limitation:
345
+ - A `complexes` entry is not a single combined protein-ligand complex PDB file. The current implementation expects `protein_pdb` plus `ligand_sdf` for each entry.
346
+
347
+ Example `complexes` config:
348
+ ```yaml
349
+ complexes:
350
+ - protein_pdb: poses/lig1_protein.pdb
351
+ ligand_sdf: poses/lig1_ligand.sdf
352
+ ligand_index: 0
353
+ - protein_pdb: poses/lig2_protein.pdb
354
+ ligand_sdf: poses/lig2_ligand.sdf
355
+ ligand_index: 0
356
+
357
+ project_compound_ids:
358
+ - lig1_ref
359
+ ```
360
+
361
+ #### Network planning options
362
+ - `compatibility_threshold` (`float`, default `0.45`): Minimum pair similarity for direct compatibility.
363
+ - `project_assignment_threshold` (`float`, default `0.35`): Minimum similarity required to attach a ligand directly to a project hub.
364
+ - `local_cluster_threshold` (`float`, default `0.55`): Minimum similarity used to identify local orphan clusters.
365
+ - `max_clash_score` (`float`, default `0.35`): Maximum tolerated per-ligand protein-clash score for compatibility.
366
+ - `diagnostic_cycle_edges_per_component` (`int`, default `1`): Number of optional extra diagnostic edges to add per connected component.
367
+
368
+ Planning behavior:
369
+ 1. Split ligands by formal charge.
370
+ 2. Build compatibility components using similarity and protein-clash filtering.
371
+ 3. Use explicit `project_compound_ids` as preferred centers when available.
372
+ 4. Build local subclusters for ligands that are similar to each other but not close to a project hub.
373
+ 5. Preserve existing edges when `operation_mode: extend`.
374
+
375
+ Reuse behavior:
376
+ - Use `existing_network_path` when you want to regenerate transformations from a saved ligand network.
377
+ - Use `existing_network_bundle_path` when you want to load a self-contained prior network including bundled proteins and reusable completed edge records.
378
+ - Use `existing_transformations_manifest_path` when you already have prepared transformation JSON files and want to reuse them directly.
379
+ - In `run` mode, `existing_transformations_manifest_path` avoids regenerating the transformation JSON files.
380
+ - `reuse_completed_edges` (`bool`, default `true`) reuses previously completed edges when compatible edge records are found next to `existing_network_path` or inside `existing_network_bundle_path`.
381
+ - `refresh_bundle` is a lightweight local-only maintenance mode. It does not plan, prepare, or run simulations; it just rebuilds a fresh `ligand_network_bundle.zip` from an existing campaign/source plus its current `edge_records`.
382
+ - Use `bundle_refresh_source_path` with `operation_mode: refresh_bundle` when you want to rebuild a portable bundle after a finished Slurm campaign.
383
+
384
+ #### Charge / protocol options
385
+ - `partial_charge_method` (`str`, default `"am1bcc"`): Partial charge method for OpenFF charge generation.
386
+ - `off_toolkit_backend` (`str`, default `"ambertools"`): OpenFF backend used for charge assignment.
387
+ - `charge_n_conformers` (`int`, default `1`): Number of conformers for charge generation.
388
+ - `charge_nagl_model` (`str | None`): Optional NAGL model name.
389
+ - `charge_processors` (`int`, default `1`): Number of processors for charge assignment.
390
+ - `overwrite_charges` (`bool`, default `false`): Overwrite existing molecule charges.
391
+ - `protocol_repeats` (`int | None`): Optional override for SepTop protocol repeats.
392
+ - `settings_overrides` (`dict[str, Any]`, default `{}`): Dot-path overrides applied to `SepTopProtocol.default_settings()`.
393
+
394
+ #### Execution / output options
395
+ - `transformation_dir_name` (`str`, default `"transformations"`): Directory name used for transformation JSON files.
396
+ - `network_dir_name` (`str`, default `"network"`): Directory name used for persisted network artifacts.
397
+ - `run_quickrun` (`bool`, default `true`): If `true`, execute `openfe quickrun` in `run` mode.
398
+ - `quickrun_extra_args` (`list[str]`, default `[]`): Extra raw arguments appended to `openfe quickrun`.
399
+ - `export_network_bundle` (`bool`, default `true`): Write a portable `ligand_network_bundle.zip` into `results/`.
400
+
401
+ Backend behavior:
402
+ - Local backend: edges are executed sequentially.
403
+ - Slurm backend: the campaign prepares the network and transformation JSONs once, then submits one Slurm child job per edge.
404
+ - `refresh_bundle` must use the local backend.
405
+
406
+ ### Outputs
407
+ For each campaign run, artifacts are written under `runs/<date>/<job-id>/`:
408
+ - `<network_dir_name>/ligand_network.json`: canonical persisted RBFE network
409
+ - `<network_dir_name>/ligand_network.graphml`: GraphML view of the network
410
+ - `results/ligand_network.json`: exportable copy of the persisted ligand network JSON
411
+ - `results/ligand_network.graphml`: exportable copy of the GraphML network view
412
+ - `results/ligand_network_bundle.zip`: portable self-contained network bundle with bundled proteins and reusable edge records
413
+ - `results/ligand_network_summary.txt`: human-readable network summary
414
+ - `results/transformations_manifest.json`: manifest of prepared transformations and edge run directories
415
+ - `results/edge_records/`: canonical per-edge reusable result cache used for extension / rerun reuse
416
+ - `transformations/`: one OpenFE transformation JSON per planned edge
417
+ - `edges/<edge-id>/`: per-edge run directories
418
+
419
+ For local `run` mode, each edge directory contains:
420
+ - `results/openfe_results.json`
421
+ - `results/openfe_stdout.log`
422
+ - `results/openfe_stderr.log`
423
+ - `openfe_run/`
424
+
425
+ Campaign-level artifacts:
426
+ - `results/result_record.json`: standardized CADDForge `Result`
427
+ - `stdout.log` / `stderr.log`: campaign workflow logs
428
+
429
+ Notes:
430
+ - In Slurm mode, edge execution status is tracked per child job.
431
+ - The parent campaign job records the planned network and submitted child jobs; child jobs own the actual per-edge simulation logs and status.
432
+ - Reuse of previously completed edges is driven by the edge cache under `results/edge_records/`. A portable bundle is sufficient for later extension if you want to move or archive a network as a single file.
433
+ ## Compressed inputs
434
+
435
+ ABFE and RBFE ligand SDF and protein PDB inputs, including paths nested in RBFE `complexes`, may be
436
+ provided with a trailing `.gz`. CADDForge expands each wrapper in runtime scratch while preserving
437
+ the original compressed path in saved configuration and provenance. Gzipped existing-network JSON
438
+ and transformation manifests are read directly so relative references remain anchored to the
439
+ manifest directory; gzip-wrapped self-contained network bundles are staged as plain bundle files.
440
+
441
+ ## Continuous integration and releases
442
+
443
+ `.github/workflows/release.yml` builds a wheel and source archive, validates
444
+ metadata, and tests the installed wheel on Linux with Python 3.10 and 3.12.
445
+ The lightweight test selection is recorded in `scripts/ci_tests.json`;
446
+ external scientific engines are validated separately. CI installs the same
447
+ wheel with its `[gui]` extra so GUI regression tests use the declared GUI
448
+ dependencies, including `caddforge-gui`.
449
+
450
+ Pushing a matching `v*` release tag publishes the tested artifacts to PyPI.
451
+ Branch pushes, pull requests and manual runs never publish. Configure a
452
+ PyPI Trusted Publisher for the `idorsia` repository, `release.yml` workflow
453
+ and `pypi` environment before pushing release tags. Runtime CADDForge
454
+ dependencies must already be available publicly; publish core and GUI before
455
+ FEP/docking, and publish docking/descriptors before MD.