Metadata-Version: 2.5
Name: caddforge-fep
Version: 0.8.0
Summary: Free energy perturbation workflows for the CADDForge toolbox.
Author-email: Manuel Sellner <manuel.sellner@idorsia.com>, Christoph Sager <christoph.sager@idorsia.com>
License: MIT
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: caddforge-core>=0.24.0
Requires-Dist: pdbfixer>=1.9
Requires-Dist: rdkit>=2024.03.1
Provides-Extra: gui
Requires-Dist: caddforge-gui>=0.7.0; extra == 'gui'
Requires-Dist: fastapi>=0.110; extra == 'gui'
Requires-Dist: python-multipart>=0.0.21; extra == 'gui'
Description-Content-Type: text/markdown

# CADDForge FEP Plugin

Install this plugin with `python -m pip install caddforge-fep`. It depends directly
on `caddforge-core`; Python imports and CLI commands still use `caddforge`.
This release (0.8.0) requires `caddforge-core>=0.24.0`; GUI integrations use `caddforge-gui>=0.7.0`.
For the complete public toolbox, including user-facing extras, install
`python -m pip install caddforge`. When migrating an existing environment, uninstall
the former `caddforge` core distribution before installing renamed core and updated plugins.

`caddforge-fep` provides free energy perturbation workflows as plugins for the CADDForge framework.

## Optional GUI dependencies
The core FEP workflows can be installed without GUI dependencies.

If you want to use the FEP GUI extension, install the optional GUI extra:
```bash
pip install "caddforge-fep[gui]"
```

## Installation notes
- It is recommended to install `openfe` and the rest of the scientific runtime stack with `conda`.
- To avoid environment conflicts, install all required `conda` packages first, and only then install CADDForge packages with `pip`.

Typical order:
```bash
conda create -n caddforge-fep python=3.10
conda activate caddforge-fep
conda install -c conda-forge openfe openff-toolkit rdkit pdbfixer
pip install caddforge-core caddforge-fep
```

Notes:
- The current GUI extension covers both `openfe-abfe` and `openfe-rbfe-septop`.
- 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.

## Implemented Tools
- [`openfe-abfe`](#tool-openfe-abfe)
- [`openfe-rbfe-septop`](#tool-openfe-rbfe-septop)

## Tool: `openfe-abfe`

### What it does
The `openfe-abfe` tool prepares, and optionally runs, OpenFE absolute binding free energy calculations through the standard CADDForge workflow/job interface. It supports:
- ligand input from a multi-record SDF with ligand selection by index
- protein input from PDB
- optional protein cleanup with `pdbfixer`
- OpenFE settings overrides through dot-path config entries
- local execution or Slurm execution with scratch work under `SLURM_TMPDIR`
- standardized CADDForge outputs (`result_record.json`, transformation JSON, OpenFE logs)

### External dependencies
These tools and libraries are required at runtime and are not installed automatically by this Python package:
- `openfe`
- `openff`
- `rdkit`
- `pdbfixer`

Installation note:
- `openfe` is best installed with `conda`.
- Install the `conda` packages first, then install `caddforge-core` / `caddforge-fep` with `pip`.

### CLI usage
```bash
caddforge run fep openfe-abfe --config abfe.yaml
```

### Python API usage
```python
from caddforge.core.execution import load_result_for_job, submit_workflow
from caddforge.plugins import get_tool

spec = get_tool("fep", "openfe-abfe")
cfg = spec.config_cls(
    ligand_sdf="ligand_neutral.sdf",
    protein_pdb="protein_fixed.pdb",
    ligand_index=0,
    backend="local",  # or "slurm"
    run_quickrun=True,
)

workflow = spec.workflow_cls(cfg)
job = submit_workflow(workflow, cfg, backend_name=cfg.backend)
result = load_result_for_job(job)
if result is not None:
    print(result.status.value, result.metrics)
```

### Example config
```yaml
ligand_sdf: ligand_neutral.sdf
protein_pdb: protein_fixed.pdb
ligand_index: 0
fix_protein_pdb: false
partial_charge_method: am1bcc
off_toolkit_backend: ambertools
host_min_distance_nm: 0.5
host_max_distance_nm: 1.5
protocol_repeats: 1
settings_overrides:
  complex_simulation_settings.real_time_analysis_interval: 10 picosecond
  complex_equil_simulation_settings.equilibration_length: 0.2 nanosecond
  complex_equil_simulation_settings.production_length: 0.5 nanosecond
  complex_equil_simulation_settings.equilibration_length_nvt: 0.10 nanosecond
  complex_simulation_settings.equilibration_length: 0.2 nanosecond
  complex_simulation_settings.production_length: 2.0 nanosecond
  complex_simulation_settings.n_replicas: 68
  solvent_equil_simulation_settings.equilibration_length: 0.1 nanosecond
  solvent_equil_simulation_settings.production_length: 0.5 nanosecond
  solvent_equil_simulation_settings.equilibration_length_nvt: 0.05 nanosecond
  solvent_simulation_settings.equilibration_length: 0.1 nanosecond
  solvent_simulation_settings.production_length: 1 nanosecond
  solvent_simulation_settings.real_time_analysis_interval: 10 picosecond
  solvent_simulation_settings.n_replicas: 60
  engine_settings.compute_platform: CUDA
  complex_lambda_settings.lambda_elec:
    [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,
      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,
      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]
  complex_lambda_settings.lambda_vdw:
    [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,
      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,
      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,
      0.86, 0.87, 0.88, 0.89, 0.9, 0.91, 0.92, 0.94, 0.96, 0.98, 1]
  complex_lambda_settings.lambda_restraints:
    [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,
      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,
      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,
      1, 1, 1, 1, 1, 1, 1]
  solvent_lambda_settings.lambda_elec:
    [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,
      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,
      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]
  solvent_lambda_settings.lambda_vdw:
    [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,
      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,
      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]
  solvent_lambda_settings.lambda_restraints:
    [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,
      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]
run_quickrun: true
quickrun_extra_args: []
```

### Tool-specific config options

#### Required / primary inputs
- `ligand_sdf` (`Path`): SDF containing the ligand set.
- `protein_pdb` (`Path`): Protein receptor PDB.
- `ligand_index` (`int`, default `0`): SDF record index to use for the ABFE calculation.

#### Optional preparation options
- `fix_protein_pdb` (`bool`, default `false`): If `true`, run `pdbfixer` before OpenFE setup.
- `partial_charge_method` (`str`, default `"am1bcc"`): Partial charge method for OpenFF charge generation.
- `off_toolkit_backend` (`str`, default `"ambertools"`): OpenFF backend used for charge assignment.
- `charge_n_conformers` (`int`, default `1`): Number of conformers for charge generation.
- `charge_nagl_model` (`str | None`): Optional NAGL model name.
- `charge_processors` (`int`, default `1`): Number of processors for charge assignment.
- `overwrite_charges` (`bool`, default `false`): Overwrite existing molecule charges.
- `require_neutral_ligand` (`bool`, default `true`): Enforce the OpenFE ABFE neutral-ligand limitation.

#### Protocol options
- `host_min_distance_nm` (`float`, default `0.5`): Minimum Boresch restraint host distance in nm.
- `host_max_distance_nm` (`float`, default `1.5`): Maximum Boresch restraint host distance in nm.
- `protocol_repeats` (`int | None`, default `1`): Number of independent protocol repeats per leg. Explicit `null` retains OpenFE's repeat default.
- `settings_overrides` (`dict[str, Any]`): The ABFE defaults below, merged with explicit dot-path overrides. Omitted, empty, or partial dictionaries retain all unspecified defaults.

| Settings group | NVT equilibration | Equilibration | Production | Lambda replicas | Analysis interval |
| --- | --- | --- | --- | --- | --- |
| Complex pre-equilibration | 0.10 ns | 0.2 ns | 0.5 ns | — | — |
| Complex alchemical simulation | — | 0.2 ns | 2.0 ns | 68 | 10 ps |
| Solvent pre-equilibration | 0.05 ns | 0.1 ns | 0.5 ns | — | — |
| Solvent alchemical simulation | — | 0.1 ns | 1 ns | 60 | 10 ps |

The default compute platform is `CUDA`. Each complex lambda array has 68 entries;
each solvent lambda array has 60 entries, with solvent restraints zero throughout.
`n_replicas` counts lambda replicas within a leg; it is distinct from independent
`protocol_repeats`. Keep each leg's three lambda-array lengths consistent with its
replica count when overriding the schedules.

The CLI, Python API, and GUI use the same defaults. Explicit
`settings_overrides.protocol_repeats` takes precedence over the top-level
`protocol_repeats`. Generated examples specify repeats only at the top level.

`settings_overrides` accepts plain values or strings of the form `<number> <unit>`, for example:
- `5 nanosecond`
- `0.15 molar`

#### Execution / output options
- `transformation_dir_name` (`str`, default `"abfe_json"`): Directory name used for OpenFE transformation JSON files.
- `run_quickrun` (`bool`, default `true`): If `true`, execute `openfe quickrun`.
- `quickrun_workdir` (`Path | None`): Optional OpenFE working directory.
- `quickrun_output` (`Path | None`): Optional output JSON path for `openfe quickrun`.
- `quickrun_extra_args` (`list[str]`, default `[]`): Extra raw arguments appended to `openfe quickrun`.

### Outputs
For each run, artifacts are written under `runs/<date>/<job-id>/`:
- `<transformation_dir_name>/`: OpenFE transformation JSON
- `results/openfe_results.json`: OpenFE quickrun output JSON
- `results/openfe_stdout.log`: OpenFE stdout
- `results/openfe_stderr.log`: OpenFE stderr
- `results/result_record.json`: standardized CADDForge `Result`
- `openfe_run/`: OpenFE working directory
- `stdout.log` / `stderr.log`: CADDForge workflow logs

### Production progress and ETA (current source)

The shared GUI and `caddforge logs JOB_ID --follow` report production iterations
and measured stage-local ETAs separately for each complex/solvent leg, repeat and
engine attempt. Estimates exclude other stages and scheduler waiting. Nonzero
early-termination targets label the ETA as an estimate to the stage maximum;
actual counters are retained when stopping early. Setup, blocking equilibration
and analysis remain explicitly indeterminate. Stopped engine measurements invalidate
the ETA even if worker heartbeats continue.

The adapter checks engine capabilities rather than requiring exact package versions.
Stage observation and production timing are qualified separately. An isolated,
deterministic OpenMM Reference CPU probe compares native and observed execution;
successful evidence is cached under `$XDG_CACHE_HOME/caddforge/engine-qualification`
(or `~/.cache`), keyed by interpreter, engine sources, versions and adapter contents.
The optional probe has a 30-second budget. Timeouts, cache outages and unavailable
probe platforms use guarded runtime reporting. Detected timing incompatibility
retains available stage reporting and disables ETA. Two advancing, consistent
production measurements are required before a running ETA appears; regressions
clear it. Qualification evidence is retained beside the engine sidecar as
`results/openfe-engine-*.qualification.json` and reported in engine logs.
Scientific dynamics, settings, stopping criteria and retry policies remain unchanged.
Install coordinated updated core/FEP workers and rebuild the worker environment
pack; already-running processes retain their original reporting.
These capabilities are included in FEP 0.8.0.

Plain `/usr/bin/env python` and versioned Python launchers generated by `conda-pack`
are resolved through the worker's `PATH`, including its working directory for relative
entries. A different Python environment is supported when its native console launcher,
OpenFE/GUFE/OpenMMtools interfaces qualify; package versions are diagnostic metadata. CADDForge stages
a hash-verified observational bundle without installing packages there. Interpreter flags,
environment-changing wrappers and incompatible packages retain the original command
with an unavailable ETA; waiting longer does not enable observation for that invocation.

Install matching updated core and FEP workers (including the Slurm environment pack).
Already running processes retain
their original reporting. No new configuration fields are required.

### ABFE workbench settings and recorded analysis

The shared Inputs → Method → Compute → Review flow now provides typed protocol
settings rather than requiring JSON edits. Method separates complex/solvent
pre-equilibration from alchemical sampling. Only independent repeats, complex and
solvent production time per replica, and temperature are initially visible. All
remaining protocol controls, including equilibration, samplers, force fields,
restraints, lambda tables and JSON, start inside a collapsed Advanced simulation
settings section. Search and changed-only filtering reveal matching groups;
changed hidden values have a visible Review changes link. Charge assignment,
input repair and execution options also start collapsed. All native settings,
defaults, overrides and resets remain available. Unit symbols
retain their correct case. Imported override literals are retained until edited.
The linked lambda tables/plots edit all three components together and explicitly
update replica counts; independent protocol repeats are a separate setting.

When OpenFE is installed on the GUI server, its models are authoritative, using
CADDForge's existing ABFE defaults. Otherwise the bundled OpenFE 1.9.1 reference
catalogue is identified explicitly and full native validation is deferred. Workers
verify the reviewed OpenFE version before preparing science. Rebuild the GUI/core/
FEP installations and Slurm environment together; existing running jobs are unchanged.
No published release or default scientific protocol change is implied. Reference
metadata attribution and MIT licence texts are retained under
[`reference_licenses`](src/cadd_fep/openfe_abfe/reference_licenses/NOTICE.md).

Result views distinguish authoritative overall binding free energy and OpenFE's
reported between-repeat spread from leg MBAR errors and standard-state corrections.
A single repeat's zero spread is explicitly qualified. Per-leg/repeat views use
native identities and show recorded forward/reverse curves (bands are the recorded
±MBAR errors, not an invented confidence level), overlap/exchange heatmaps and any
recorded prepared structure. Complete curve/matrix CSVs and original outputs remain
available. Diagnostics are normalized in the worker, with bounded views in the web
server. Missing diagnostics and partial outcomes are explicit; preparation-only jobs
do not claim a binding free energy. Legacy jobs retain bounded summary/File fallback.

Custom force-field XML Includes are retained together with verified content copies;
allocated workers stage required structures and custom XML on scratch. The canonical
transformation retains durable XML references, while its execution variant uses the
local copies. These are path changes only, preserving scientific parameter values.

## Tool: `openfe-rbfe-septop`

### What it does
The `openfe-rbfe-septop` tool plans, prepares, and optionally runs separate-topology RBFE calculations with OpenFE SepTop. It supports:
- input as one shared protein plus many ligand poses, or as many aligned protein-plus-ligand input pairs
- explicit project-compound selection via config
- automatic ligand-network generation with:
  - charge bucketing
  - compatibility grouping based on similarity and protein-clash filtering
  - project-centered radial maps
  - local orphan-cluster hubs
  - optional diagnostic cycle edges
- extension of an existing persisted ligand network without removing existing edges
- portable self-contained network bundles with bundled proteins plus reusable completed edge records
- bundle refresh mode to rebuild a fresh portable bundle from an existing finished campaign
- persisted ligand-network JSON plus GraphML export
- local sequential execution or Slurm fan-out submission with one child job per transformation edge
- an interactive GUI workflow with network visualization and manual editing

### External dependencies
These tools and libraries are required at runtime and are not installed automatically by this Python package:
- `openfe`
- `openff`
- `rdkit`

Notes:
- This workflow assumes ligand poses are already meaningful and aligned in the protein frame.
- 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.
- `openfe` is best installed with `conda`, and all `conda` dependencies should be installed before adding the CADDForge packages with `pip`.

### CLI usage
```bash
caddforge run fep openfe-rbfe-septop --config rbfe_septop.yaml
```

Refresh a portable bundle from an existing finished campaign:
```bash
caddforge run fep openfe-rbfe-septop --config rbfe_refresh_bundle.yaml
```

### Python API usage
```python
from caddforge.core.execution import load_result_for_job, submit_workflow
from caddforge.plugins import get_tool

spec = get_tool("fep", "openfe-rbfe-septop")
cfg = spec.config_cls(
    ligand_sdf="ligands.sdf",
    ligand_sdfs=[],
    protein_pdb="protein.pdb",
    project_compound_ids=["proj_anchor_01", "proj_anchor_02"],
    operation_mode="run",
    backend="local",  # or "slurm"
    protocol_repeats=3,
)

workflow = spec.workflow_cls(cfg)
job = submit_workflow(workflow, cfg, backend_name=cfg.backend)
result = load_result_for_job(job)
if result is not None:
    print(result.status.value, result.metrics)
```

### Example config
```yaml
operation_mode: run
ligand_sdf: ligands.sdf
ligand_sdfs: []
protein_pdb: protein.pdb
complexes: []
project_compound_ids:
  - proj_anchor_01
  - proj_anchor_02
bundle_refresh_source_path: null
existing_network_path: null
existing_network_bundle_path: null
existing_transformations_manifest_path: null
export_network_bundle: true
reuse_completed_edges: true

partial_charge_method: am1bcc
off_toolkit_backend: ambertools
protocol_repeats: 3
settings_overrides:
  engine_settings.compute_platform: CUDA

compatibility_threshold: 0.45
project_assignment_threshold: 0.35
local_cluster_threshold: 0.55
max_clash_score: 0.35
diagnostic_cycle_edges_per_component: 1

run_quickrun: true
quickrun_extra_args: []
```

### Tool-specific config options

#### Required / primary inputs
Use either:
- `ligand_sdf` and/or `ligand_sdfs`, together with `protein_pdb`

Or:
- `complexes`

Primary fields:
- `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.
- `ligand_sdf` (`Path | None`): Optional SDF containing ligands already placed in the protein frame.
- `ligand_sdfs` (`list[Path]`, default `[]`): Optional list of SDF files used with the shared protein. Each file may contain one or many ligands.
- `protein_pdb` (`Path | None`): Shared aligned protein PDB used with `ligand_sdf` and/or `ligand_sdfs`.
- `complexes` (`list[SeptopComplexInput]`, default `[]`): Alternative aligned per-ligand input pairs, each containing a `protein_pdb` and a `ligand_sdf`.
- `project_compound_ids` (`list[str]`, default `[]`): Explicit ligand identifiers used as project anchors.
- `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.
- `existing_network_path` (`Path | None`): Persisted network JSON used for `extend`, `prepare`, or `run` from an existing network.
- `existing_network_bundle_path` (`Path | None`): Portable self-contained RBFE bundle zip used for `extend`, `prepare`, or `run`.
- `existing_transformations_manifest_path` (`Path | None`): Existing `transformations_manifest.json` used to reuse already prepared transformation JSON files without regenerating them.

`SeptopComplexInput` fields:
- `ligand_sdf` (`Path`): Ligand SDF file.
- `protein_pdb` (`Path`): Protein PDB file for that ligand pose.
- `ligand_index` (`int`, default `0`): Ligand record to use from the SDF.
- `ligand_id` (`str | None`): Reserved identifier field; current implementation derives IDs from ligand names/properties.

Current limitation:
- 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.

Example `complexes` config:
```yaml
complexes:
  - protein_pdb: poses/lig1_protein.pdb
    ligand_sdf: poses/lig1_ligand.sdf
    ligand_index: 0
  - protein_pdb: poses/lig2_protein.pdb
    ligand_sdf: poses/lig2_ligand.sdf
    ligand_index: 0

project_compound_ids:
  - lig1_ref
```

#### Network planning options
- `compatibility_threshold` (`float`, default `0.45`): Minimum pair similarity for direct compatibility.
- `project_assignment_threshold` (`float`, default `0.35`): Minimum similarity required to attach a ligand directly to a project hub.
- `local_cluster_threshold` (`float`, default `0.55`): Minimum similarity used to identify local orphan clusters.
- `max_clash_score` (`float`, default `0.35`): Maximum tolerated per-ligand protein-clash score for compatibility.
- `diagnostic_cycle_edges_per_component` (`int`, default `1`): Number of optional extra diagnostic edges to add per connected component.

Planning behavior:
1. Split ligands by formal charge.
2. Build compatibility components using similarity and protein-clash filtering.
3. Use explicit `project_compound_ids` as preferred centers when available.
4. Build local subclusters for ligands that are similar to each other but not close to a project hub.
5. Preserve existing edges when `operation_mode: extend`.

Reuse behavior:
- Use `existing_network_path` when you want to regenerate transformations from a saved ligand network.
- Use `existing_network_bundle_path` when you want to load a self-contained prior network including bundled proteins and reusable completed edge records.
- Use `existing_transformations_manifest_path` when you already have prepared transformation JSON files and want to reuse them directly.
- In `run` mode, `existing_transformations_manifest_path` avoids regenerating the transformation JSON files.
- `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`.
- `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`.
- Use `bundle_refresh_source_path` with `operation_mode: refresh_bundle` when you want to rebuild a portable bundle after a finished Slurm campaign.

#### Charge / protocol options
- `partial_charge_method` (`str`, default `"am1bcc"`): Partial charge method for OpenFF charge generation.
- `off_toolkit_backend` (`str`, default `"ambertools"`): OpenFF backend used for charge assignment.
- `charge_n_conformers` (`int`, default `1`): Number of conformers for charge generation.
- `charge_nagl_model` (`str | None`): Optional NAGL model name.
- `charge_processors` (`int`, default `1`): Number of processors for charge assignment.
- `overwrite_charges` (`bool`, default `false`): Overwrite existing molecule charges.
- `protocol_repeats` (`int | None`): Optional override for SepTop protocol repeats.
- `settings_overrides` (`dict[str, Any]`, default `{}`): Dot-path overrides applied to `SepTopProtocol.default_settings()`.

#### Execution / output options
- `transformation_dir_name` (`str`, default `"transformations"`): Directory name used for transformation JSON files.
- `network_dir_name` (`str`, default `"network"`): Directory name used for persisted network artifacts.
- `run_quickrun` (`bool`, default `true`): If `true`, execute `openfe quickrun` in `run` mode.
- `quickrun_extra_args` (`list[str]`, default `[]`): Extra raw arguments appended to `openfe quickrun`.
- `export_network_bundle` (`bool`, default `true`): Write a portable `ligand_network_bundle.zip` into `results/`.

Backend behavior:
- Local backend: edges are executed sequentially.
- Slurm backend: the campaign prepares the network and transformation JSONs once, then submits one Slurm child job per edge.
- `refresh_bundle` must use the local backend.

### Outputs
For each campaign run, artifacts are written under `runs/<date>/<job-id>/`:
- `<network_dir_name>/ligand_network.json`: canonical persisted RBFE network
- `<network_dir_name>/ligand_network.graphml`: GraphML view of the network
- `results/ligand_network.json`: exportable copy of the persisted ligand network JSON
- `results/ligand_network.graphml`: exportable copy of the GraphML network view
- `results/ligand_network_bundle.zip`: portable self-contained network bundle with bundled proteins and reusable edge records
- `results/ligand_network_summary.txt`: human-readable network summary
- `results/transformations_manifest.json`: manifest of prepared transformations and edge run directories
- `results/edge_records/`: canonical per-edge reusable result cache used for extension / rerun reuse
- `transformations/`: one OpenFE transformation JSON per planned edge
- `edges/<edge-id>/`: per-edge run directories

For local `run` mode, each edge directory contains:
- `results/openfe_results.json`
- `results/openfe_stdout.log`
- `results/openfe_stderr.log`
- `openfe_run/`

Campaign-level artifacts:
- `results/result_record.json`: standardized CADDForge `Result`
- `stdout.log` / `stderr.log`: campaign workflow logs

Notes:
- In Slurm mode, edge execution status is tracked per child job.
- The parent campaign job records the planned network and submitted child jobs; child jobs own the actual per-edge simulation logs and status.
- 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.
## Compressed inputs

ABFE and RBFE ligand SDF and protein PDB inputs, including paths nested in RBFE `complexes`, may be
provided with a trailing `.gz`. CADDForge expands each wrapper in runtime scratch while preserving
the original compressed path in saved configuration and provenance. Gzipped existing-network JSON
and transformation manifests are read directly so relative references remain anchored to the
manifest directory; gzip-wrapped self-contained network bundles are staged as plain bundle files.

## Continuous integration and releases

`.github/workflows/release.yml` builds a wheel and source archive, validates
metadata, and tests the installed wheel on Linux with Python 3.10 and 3.12.
The lightweight test selection is recorded in `scripts/ci_tests.json`;
external scientific engines are validated separately. CI installs the same
wheel with its `[gui]` extra so GUI regression tests use the declared GUI
dependencies, including `caddforge-gui`.

Pushing a matching `v*` release tag publishes the tested artifacts to PyPI.
Branch pushes, pull requests and manual runs never publish. Configure a
PyPI Trusted Publisher for the `idorsia` repository, `release.yml` workflow
and `pypi` environment before pushing release tags. Runtime CADDForge
dependencies must already be available publicly; publish core and GUI before
FEP/docking, and publish docking/descriptors before MD.
