Pipeline Examples¶
These examples show complete pipeline YAML files and the artifact flow between stages. Replace input paths and config filenames with project-specific files.
Run any example with:
These are advanced configuration templates. They require prepared molecular/data inputs and the runtimes listed in the example library. Start with the small runnable pipeline. The supplied configs are schema-checked; full external-engine execution is a separate validation step. Replace all scientific paths and box coordinates before running.
Download configurations¶
- Docking Screen and Rescore
- Gnina Flexible Complex Artifacts
- Flexible Gnina to OpenBPMD to MD to ABFE
- Minimize a Multi-Pose SDF
- Dock, Extract, and Minimize Poses
- Pose-Level MD Selection
- Descriptor, Model, and Candidate Filter
- Result Collection and Diagnostics
Docking Screen and Rescore¶
This pipeline screens a multi-record SDF with UniDock, filters by score, retains the selected poses, and rescores them with Gnina.
version: 1
name: docking-screen-rescore
defaults:
backend: local
registry_path: runs/job_registry.sqlite
job_name: docking-screen-rescore
stages:
- id: unidock_screen
tool: docking/unidock
config: configs/unidock.yaml
scatter:
# Split the ligand_multisdf_path field from configs/unidock.yaml into independent shards.
by: ligand_multisdf_path
chunk_size: 50000
outputs:
# Expose Result.paths["best_scores_csv"] as unidock_screen.scores.
scores: best_scores_csv
# Expose Result.paths["poses_dir"] as unidock_screen.poses.
poses: poses_dir
- id: top_unidock_scores
type: filter.tabular
inputs:
# Read the score table produced by the previous stage.
table: unidock_screen.scores
config:
sort_by: best_score
ascending: true
top_n: 1000
output_name: top_unidock_scores.csv
- id: retain_unidock_poses
type: filter.molecules
inputs:
# Filter the pose directory/path collection from UniDock.
molecules: unidock_screen.poses
# Use the filtered table from filter.tabular; this built-in always emits filtered_table.
ids: top_unidock_scores.filtered_table
config:
# The values in this column are matched against pose metadata or filenames.
id_column: ligand
- id: gnina_rescore
tool: docking/gnina
config: configs/gnina_rescore.yaml
inputs:
# Bind the retained pose path collection to GninaConfig.ligand_paths.
ligand_paths: retain_unidock_poses.molecules
outputs:
# Expose Gnina result paths for downstream stages.
scores: best_scores_csv
poses: poses_dir
Artifact flow:
unidock_screen.scorespoints to the reduced UniDockbest_scores.csv.unidock_screen.posespoints to the UniDock pose directory or path collection.top_unidock_scores.filtered_tableis the retained top-N table.retain_unidock_poses.moleculesis a retained pose path collection.gnina_rescore.scoresis the rescoring table for downstream ranking.
Gnina Flexible Complex Artifacts¶
This pipeline runs flexible Gnina docking and uses the default Gnina output ports. The flexible
artifact ports are optional: they exist only when flexible docking, write_flex_receptor_output, and
write_flexible_complexes are enabled in configs/gnina_flexible.yaml.
version: 1
name: gnina-flexible-artifacts
defaults:
backend: local
registry_path: runs/job_registry.sqlite
job_name: gnina-flexible-artifacts
stages:
- id: gnina_flexible
tool: docking/gnina
config: configs/gnina_flexible.yaml
- id: first_flexible_pose_per_ligand
type: filter.tabular
inputs:
# This is the flexible_complexes_manifest.csv emitted by Gnina.
table: gnina_flexible.flexible_complexes_manifest
config:
group_by: ligand
sort_by: pose_rank
ascending: true
top_n_per_group: 1
output_name: first_flexible_pose_per_ligand.csv
- id: retain_ligand_pose_sdfs
type: filter.molecules
inputs:
# Manifest-backed path collection from the ligand_pose_file column.
molecules: gnina_flexible.flexible_ligand_poses
ids: first_flexible_pose_per_ligand.filtered_table
config:
path_column: ligand_pose_file
- id: retain_protein_pdbs
type: filter.molecules
inputs:
# Manifest-backed path collection from the protein_file column.
molecules: gnina_flexible.flexible_proteins
ids: first_flexible_pose_per_ligand.filtered_table
config:
path_column: protein_file
- id: retain_complex_pdbs
type: filter.molecules
inputs:
# Manifest-backed path collection from the complex_file column.
molecules: gnina_flexible.flexible_complexes
ids: first_flexible_pose_per_ligand.filtered_table
config:
path_column: complex_file
Artifact flow:
gnina_flexible.scoresis the normalbest_scores.csv.gnina_flexible.flexible_complexes_manifestis the per-pose flexible artifact manifest.gnina_flexible.flexible_ligand_poses,gnina_flexible.flexible_proteins, andgnina_flexible.flexible_complexesare manifest-backed path collections.- The retained artifacts contain matched ligand-only SDFs, protein-only PDBs, and complex PDBs for the selected flexible pose per ligand.
Flexible Gnina to OpenBPMD to MD to ABFE¶
This pipeline keeps ligands whose best Gnina score is <= -12, selects the top 4 flexible poses per
retained ligand by CNNscore (larger is better), runs OpenBPMD on those protein-ligand complexes,
keeps the pose with the best OpenBPMD comp_score, runs 10 ns plain MD for that complex, centers
and aligns the MD trajectory, extracts a whole-frame cluster representative, and runs OpenFE ABFE on
the representative protein-only PDB plus representative ligand-only SDF.
version: 1
name: flexible-gnina-openbpmd-md-abfe
defaults:
backend: slurm
registry_path: runs/job_registry.sqlite
job_name: flexible-gnina-openbpmd-md-abfe
stages:
- id: gnina_flexible
tool: docking/gnina
config: configs/gnina_flexible.yaml
- id: retained_ligands
type: filter.tabular
inputs:
# One row per ligand; best_score is lower-is-better affinity.
table: gnina_flexible.scores
config:
filters:
- column: best_score
op: "<="
value: -12.0
columns: [ligand, best_score]
output_name: retained_ligands.csv
- id: top4_flexible_poses
type: filter.tabular
inputs:
# One row per generated flexible pose, with complex_file/protein_file/ligand_pose_file.
table: gnina_flexible.flexible_complexes_manifest
# Retain all poses belonging to ligands that passed the best-score cutoff.
ids: retained_ligands.filtered_table
config:
id_column: ligand
ids_column: ligand
group_by: ligand
sort_by: CNNscore
ascending: false
top_n_per_group: 4
columns:
- ligand
- pose_rank
- minimizedAffinity
- CNNscore
- complex_file
- protein_file
- ligand_pose_file
output_name: top4_flexible_poses.csv
- id: retain_top4_complexes
type: filter.molecules
inputs:
# OpenBPMD should receive the full protein-ligand complex PDB.
molecules: gnina_flexible.flexible_complexes
ids: top4_flexible_poses.filtered_table
config:
path_column: complex_file
- id: openbpmd_top4
tool: md/openbpmd
config: configs/openbpmd_pose.yaml
inputs:
# Bind each selected complex PDB to OpenBPMDConfig.complex_pdb_path.
complex_pdb_path: retain_top4_complexes.molecules
scatter:
by: complex_pdb_path
chunk_size: 1
outputs:
# results.collect resolves the sibling result_record.json from this path.
result_record: results_json
- id: collect_openbpmd
type: results.collect
inputs:
results: openbpmd_top4.result_record
# Preserve ligand, pose_rank, and the exact flexible artifact paths.
table: top4_flexible_poses.filtered_table
config:
metrics:
comp_score: metrics.comp_score
pose_score: metrics.pose_score
output_name: openbpmd_top4_metrics.csv
- id: best_openbpmd_pose
type: filter.tabular
inputs:
table: collect_openbpmd.table
config:
group_by: ligand
sort_by: comp_score
ascending: true
top_n_per_group: 1
columns:
- ligand
- pose_rank
- comp_score
- pose_score
- complex_file
- protein_file
- ligand_pose_file
- result_file
output_name: best_openbpmd_pose_per_ligand.csv
- id: retain_best_complexes
type: filter.molecules
inputs:
# Plain MD receives the full protein-ligand complex PDB for the selected pose.
molecules: gnina_flexible.flexible_complexes
ids: best_openbpmd_pose.filtered_table
config:
path_column: complex_file
- id: retain_best_ligands
type: filter.molecules
inputs:
# Plain MD needs the ligand-only SDF for non-standard ligand parameterization.
molecules: gnina_flexible.flexible_ligand_poses
ids: best_openbpmd_pose.filtered_table
config:
path_column: ligand_pose_file
- id: plain_md_10ns
tool: md/plain-md
config:
simulation_time_ns: 10.0
trajectory_filename: traj.xtc
output_dir_name: output
inputs:
system_pdb_path: retain_best_complexes.molecules
molecule_sdf_paths: retain_best_ligands.molecules
scatter:
# Scatter over complex PDBs. The matching ligand SDF is bound as a one-item
# molecule_sdf_paths list for the same shard.
by: system_pdb_path
chunk_size: 1
- id: center_align_md
tool: md/center-and-align
config:
center_selection: protein or resname LIG
keep_selection: protein or resname LIG
align_selection: protein and backbone
output_trajectory_filename: traj-centered.xtc
output_structure_filename: traj-centered.pdb
inputs:
# The plain-md trajectory uses the prepared solvated system topology.
topology_path: plain_md_10ns.prepared_structure
trajectory_path: plain_md_10ns.trajectory
scatter:
by: trajectory_path
chunk_size: 1
outputs:
centered_structure: output_structure
centered_trajectory: output_trajectory
- id: cluster_representative
tool: md/cluster-representative
config:
ligand_resname: LIG
cluster_selection: all
extract_selection: all
last_ns: 10.0
output_complex_filename: representative_complex.pdb
output_protein_filename: representative_protein.pdb
output_ligand_sdf_filename: representative_ligand.sdf
inputs:
topology: center_align_md.centered_structure
trajectory: center_align_md.centered_trajectory
# The ligand template preserves bond orders, hydrogens, and formal charges in the exported SDF.
ligand_template: retain_best_ligands.molecules
scatter:
by: trajectory_path
chunk_size: 1
- id: openfe_abfe
tool: fep/openfe-abfe
config: configs/openfe_abfe.yaml
inputs:
# ABFE receives separate files from the representative frame, not the original docked pose.
ligand_sdf: cluster_representative.ligand
protein_pdb: cluster_representative.protein
scatter:
# Scatter over ligand_sdf; the matching representative protein_pdb is bound by row order.
by: ligand_sdf
chunk_size: 1
outputs:
results: results_json
Input choices:
- Flexible Gnina must be configured with a PDB receptor,
output_format: sdf,write_flex_receptor_output: true, andwrite_flexible_complexes: true. - OpenBPMD receives
complex_pdb_pathfromgnina_flexible.flexible_complexesbecause it operates on a full protein-ligand complex. - Plain MD receives the selected complex PDB as
system_pdb_pathand the matching ligand-only SDF as a one-itemmolecule_sdf_pathslist for ligand parameterization. - The
LIGresidue name incenter_align_mdandcluster_representativemust matchflexible_complex_ligand_resnamefrom the Gnina config. center_align_mdusesplain_md_10ns.prepared_structureas topology andplain_md_10ns.trajectoryas trajectory.cluster_representativeuses the centered structure/trajectory and the matching ligand template SDF.cluster_selection: allmakes the representative frame come from the whole retained centered system rather than ligand-only clustering.- ABFE receives
ligand_sdfandprotein_pdbfrom the cluster representative. It does not receive the original docked ligand SDF, the original flexible protein PDB, or the representative complex PDB.
Minimize a Multi-Pose SDF¶
Use md/minimize to clean up already prepared poses
against one receptor snapshot. Install a plugin revision containing minimize
(see the version matrix). Each SDF record must
contain one explicitly hydrogenated 3D molecule with authoritative chemistry,
already positioned in the receptor coordinate frame. The receptor PDB must have
complete protein heavy atoms and no target ligand. A separate ligand file is not
aligned automatically.
Download the SDF scatter pipeline.
Save it as pipeline_scatter.yaml, update the paths, and run:
# Poses must share receptor coordinates and contain explicit hydrogens.
# Paths resolve relative to the submission working directory.
version: 1
name: pose-cleanup
defaults:
backend: local
run_directory: runs/pose-cleanup
registry_path: runs/job_registry.sqlite
export_results: false
stages:
- id: minimize
tool: md/minimize
config:
protein_pdb_path: receptor.pdb
ligand_path: poses.sdf
mode: ligand_only
scatter:
by: ligand_path
chunk_size: 1
outputs:
results: result_record
ligands: minimized_ligand
- id: collect
type: results.collect
inputs:
results: minimize.results
config:
metrics:
quality_passed: metrics.quality_passed
convergence: metrics.convergence_status
ligand_rmsd_angstrom: metrics.ligand_heavy_atom_rmsd_angstrom
minimized_ligand_file: paths.minimized_ligand
- id: accepted
type: filter.tabular
inputs:
table: collect.table
config:
filters:
- column: quality_passed
op: '=='
value: 1
minimize.results is the collection of canonical result records;
minimize.ligands is the merged minimized SDF. collect.table has one row per
job, while accepted.filtered_table retains only rows without quality flags.
Its minimized_ligand_file column points to each final ligand SDF. No accepted
rows is a valid result: inspect the rejected diagnostics before changing review
thresholds. The table filter does not itself filter the merged SDF.
SDF titles and properties survive minimization. For explicit ligand and
pose_id columns joined by core scatter provenance, use the extracted path
collection in the next example. Total minimization energy is local cleanup
information, not a binding score or a basis for ranking unrelated ligands.
Dock, Extract, and Minimize Poses¶
This example runs rigid-receptor GNINA docking, extracts one SDF per pose, restores explicit hydrogens, and scatters minimization over that path collection. It uses the same receptor PDB for docking and cleanup. Supply a GNINA executable and the GNINA requirements, plus the minimize runtime.
Download the docking/minimization pipeline.
Run it with caddforge pipeline run --config pipeline_docking.yaml after replacing
the data/ paths.
# Rigid-receptor GNINA docking followed by local OpenMM pose cleanup.
# Supply GNINA on PATH and authoritative ligand chemistry in the input SDF.
version: 1
name: dock-extract-minimize
defaults:
backend: local
run_directory: runs/dock-extract-minimize
registry_path: runs/job_registry.sqlite
export_results: false
stages:
- id: dock
tool: docking/gnina
config:
receptor_path: data/receptor.pdb
ligand_path: data/ligands.sdf
autobox_ligand_path: data/reference_ligand.sdf
output_format: sdf
outputs:
poses: poses_dir
scores: best_scores_csv
- id: extract
type: poses.extract
inputs:
poses: dock.poses
table: dock.scores
config:
ligand_column: ligand
path_column: pose_file
output_name: pose_manifest.csv
- id: hydrogenate
type: molecules.hydrogenate
inputs:
molecules: extract.poses
table: extract.manifest
config:
on_template_failure: fail
on_failure: fail
add_coords: true
- id: minimize
tool: md/minimize
config:
protein_pdb_path: data/receptor.pdb
mode: sidechains_free_backbone_restrained
backbone_restraint_strength: moderate
strict_quality: false
inputs:
ligand: hydrogenate.molecules
scatter:
by: ligand_path
chunk_size: 1
outputs:
results: result_record
ligands: minimized_ligand
- id: collect
type: results.collect
inputs:
results: minimize.results
config:
metrics:
quality_passed: metrics.quality_passed
quality_flag_count: metrics.quality_flag_count
convergence: metrics.convergence_status
ligand_rmsd_angstrom: metrics.ligand_heavy_atom_rmsd_angstrom
minimized_complex_file: paths.minimized_complex
minimized_ligand_file: paths.minimized_ligand
diagnostics_file: paths.diagnostics
output_name: minimized_pose_metrics.csv
- id: accepted
type: filter.tabular
inputs:
table: collect.table
config:
filters:
- column: quality_passed
op: '=='
value: 1
output_name: accepted_minimized_poses.csv
extract.posescontains one SDF per pose;extract.manifestrecords ligand and pose IDs. Hydrogenation preserves those IDs in its output manifest.- The minimize input port is
ligand; the scatter config field isligand_path. Each shard receives exactly one pose. results.collectautomatically joins the path-collection scatter manifest, preservingligandandpose_idalongside minimization metrics.accepted.filtered_tablepoints to the minimized ligand and complex files. Original provenance columns such aspose_fileoroutput_filecan still identify the input poses; consume the explicitminimized_*_filecolumns for final structures, including any relaxed receptor coordinates.
Direct hydrogen addition relies on the docking SDF's chemistry and cannot recover
missing bond orders or an intended protonation state. Confirm those upstream. When
an upstream stage provides authoritative templates, bind its artifact as
templates on hydrogenate (see template-based repair).
on_template_failure: fail prevents fallback if a configured template repair
fails; it does not require a template to be present.
For flexible-receptor docking, carry the matching per-pose receptor/complex through
paired scatter; do not combine all ligand poses with the original rigid receptor.
Keep strict_quality: false while collecting finite flagged poses for review.
Strict mode deliberately fails rejected jobs and may stop the pipeline before
collection. Hard chemistry, parameterization, and nonfinite-state errors always
fail. A quality pass alone does not establish pose accuracy.
For Slurm, change defaults.backend to slurm and supply the environment/resources
from the Slurm guide. Choose resources per tool: the minimize
stage defaults to CPU even when the upstream docking stage uses a GPU.
Pose-Level MD Selection¶
This pipeline extracts one file per docking pose, repairs explicit hydrogens after docking, scatters OpenBPMD over each repaired pose, collects per-pose metrics, selects the best pose per ligand, and retains exact pose files.
version: 1
name: pose-level-openbpmd-selection
defaults:
backend: slurm
registry_path: runs/job_registry.sqlite
job_name: pose-level-openbpmd-selection
stages:
- id: gnina_docking
tool: docking/gnina
config: configs/gnina_docking.yaml
outputs:
# Expose the score CSV and pose directory from Result.paths.
scores: best_scores_csv
poses: poses_dir
- id: extract_poses
type: poses.extract
inputs:
# Split every SDF/SDFGZ pose from Gnina into a normalized one-pose SDF file.
poses: gnina_docking.poses
# Optional but useful: carries score-table metadata into the pose manifest.
table: gnina_docking.scores
config:
output_name: pose_manifest.csv
ligand_column: ligand
path_column: pose_file
- id: repair_pose_hydrogens
type: molecules.hydrogenate
inputs:
# Hydrogenate the extracted pose path collection before handing poses to OpenBPMD.
molecules: extract_poses.poses
# Reuse the pose manifest so ligand, pose_id, and pose_file provenance are preserved.
table: extract_poses.manifest
config:
ligand_column: ligand
pose_id_column: pose_id
# Without a templates input, hydrogens are added directly to each pose.
# See the built-in stage docs for template-based bond-order repair.
on_template_failure: warn
on_failure: fail
add_coords: true
output_name: hydrogenated_pose_manifest.csv
- id: openbpmd_per_pose
tool: md/openbpmd
config: configs/openbpmd_pose.yaml
inputs:
# Bind each hydrogenated pose file to OpenBPMDConfig.ligand_path.
ligand_path: repair_pose_hydrogens.molecules
scatter:
# Path-collection scatter submits each ligand_path shard independently.
by: ligand_path
chunk_size: 1
outputs:
# Expose canonical per-shard result records for collection.
result_record: result_record_json
- id: collect_pose_metrics
type: results.collect
inputs:
# Collect metrics from every scattered OpenBPMD result record.
results: openbpmd_per_pose.result_record
# Join explicit hydrogenation metadata to the collected metrics.
table: repair_pose_hydrogens.manifest
config:
metrics:
comp_score: metrics.comp_score
pose_score: metrics.pose_score
output_name: openbpmd_pose_metrics.csv
- id: best_pose_per_ligand
type: filter.tabular
inputs:
# results.collect always emits collect_pose_metrics.table.
table: collect_pose_metrics.table
config:
group_by: ligand
sort_by: comp_score
ascending: true
top_n_per_group: 1
output_name: best_pose_per_ligand.csv
- id: retain_best_poses
type: filter.molecules
inputs:
# Filter the hydrogenated pose files, not the pre-repair Gnina poses.
molecules: repair_pose_hydrogens.molecules
ids: best_pose_per_ligand.filtered_table
config:
# Prefer exact file paths from the filtered table over filename matching.
path_column: output_file
Artifact flow:
extract_poses.posesis a path collection with one SDF per pose.repair_pose_hydrogens.moleculesis the repaired path collection used by OpenBPMD.- Path-collection scatter creates one OpenBPMD shard per repaired pose.
results.collectjoins per-pose metrics to pose/hydrogenation provenance.filter.tabularusesgroup_by: ligand; this selects the best pose per ligand rather than the globally best poses.retain_best_poses.moleculescontains exact selected repaired pose files.
Descriptor, Model, and Candidate Filter¶
This pipeline computes descriptors, trains an ML model, and filters candidates by predicted value. The example assumes the descriptor and ML configs agree on the generated table columns.
version: 1
name: descriptor-ml-filter
defaults:
backend: local
registry_path: runs/job_registry.sqlite
stages:
- id: psa_descriptors
tool: descriptors/ani-psa
config: configs/ani_psa_batch.yaml
outputs:
# Expose Result.paths["results_csv"] as psa_descriptors.table.
table: results_csv
- id: train_gp
tool: ml/simple-gp
config:
train_path: data/training_descriptors.csv
target_column: measured_property
# Use the table artifact from the descriptor stage as the GP test/prediction set.
test_path: psa_descriptors.table
feature_columns: [ani_psa, heavy_atom_count]
kernel: rbf
mode: train
model_path: artifacts/simple_gp_model.joblib
outputs:
# Expose Result.paths["predictions_csv"] as train_gp.predictions.
predictions: predictions_csv
- id: promising_candidates
type: filter.tabular
inputs:
table: train_gp.predictions
config:
filters:
- column: y_pred
op: ">="
value: 7.0
sort_by: y_pred
ascending: false
top_n: 250
output_name: promising_candidates.csv
Artifact flow:
psa_descriptors.tableis the descriptor table.train_gp.predictionsis the model prediction table.promising_candidates.filtered_tableis the ranked filtered candidate list.
Result Collection and Diagnostics¶
This pipeline collects scattered JSON results, filters the collected metrics, and produces a distribution diagnostic report.
version: 1
name: collect-and-diagnose
defaults:
backend: local
registry_path: runs/job_registry.sqlite
stages:
- id: scattered_transfer
tool: qm/transfer-free-energy
config: configs/transfer_free_energy.yaml
scatter:
# Split the ligand SDF into one-molecule transfer calculations.
by: ligand_sdf
chunk_size: 1
outputs:
# Expose per-shard canonical result records.
result_record: result_record_json
- id: collect_transfer
type: results.collect
inputs:
results: scattered_transfer.result_record
config:
metrics:
# Copy payload["metrics"]["delta_g_kcal_mol"] into a table column.
delta_g_kcal_mol: metrics.delta_g_kcal_mol
output_name: transfer_metrics.csv
- id: filter_transfer
type: filter.tabular
inputs:
# results.collect emits collect_solvation.table.
table: collect_transfer.table
config:
filters:
- column: delta_g_kcal_mol
op: "<="
value: -3.0
sort_by: delta_g_kcal_mol
ascending: true
output_name: favorable_transfer.csv
- id: distribution_report
tool: datascience/distribution-diagnostics
config:
# Built-in stage outputs can be bound into inline tool config fields.
csv_path: filter_transfer.filtered_table
column: delta_g_kcal_mol
bins: 50
include_log: false
outputs:
report: report_json
plot: plot_png
Artifact flow:
scattered_transfer.result_recordis a reduced collection of result records.collect_transfer.tablejoins JSON metrics to scatter provenance.filter_transfer.filtered_tableis a subset table.distribution_report.reportanddistribution_report.plotsummarize the filtered distribution.