🆕 Serialize Protein-Ligand Interactions
Problem
You want to perceive protein-ligand interaction and serialize them to a file
.oeb or .json.
Ingredients
|
Difficulty Level
🌶️ 🌶️
Download
Source Code
interactions2oeb
#!/usr/bin/env python3
# (C) 2026 Cadence Design Systems, Inc. (Cadence)
# All rights reserved.
# TERMS FOR USE OF SAMPLE CODE The software below ("Sample Code") is
# provided to current licensees or subscribers of Cadence products or
# SaaS offerings (each a "Customer").
# Customer is hereby permitted to use, copy, and modify the Sample Code,
# subject to these terms. Cadence claims no rights to Customer's
# modifications. Modification of Sample Code is at Customer's sole and
# exclusive risk. Sample Code may require Customer to have a then
# current license or subscription to the applicable Cadence offering.
# THE SAMPLE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED. OPENEYE DISCLAIMS ALL WARRANTIES, INCLUDING, BUT
# NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. In no event shall Cadence be
# liable for any damages or liability in connection with the Sample Code
# or its use.
"""Serialize protein-ligand interactions to OEB/JSON."""
import argparse
import os
import pathlib
import sys
import rich.console
from openeye import oechem
from rich.progress import BarColumn, Progress, TextColumn, TimeElapsedColumn
from rich_argparse import HelpPreviewAction, RichHelpFormatter
__SCRIPT_NAME__ = pathlib.Path(__file__).absolute().stem
__SCRIPT_DESC__ = "Serialize protein-ligand interactions to OEB/JSON."
__SCRIPT_TOOLKITS__ = ["oechem"]
__SCRIPT_KEYWORDS__ = [
"perception",
"active-site",
"protein-ligand",
"interactions",
"serialization",
]
__SCRIPT_CATEGORIES__ = ["protein-ligand interactions"]
def parse_options() -> argparse.Namespace:
"""Set up command line options."""
parser = argparse.ArgumentParser(
add_help=True,
formatter_class=RichHelpFormatter,
description="[yellow]" + __SCRIPT_DESC__ + "[/yellow]",
)
parser.add_argument("--help-image", action=HelpPreviewAction)
parser.add_argument(
"--save-console-svg",
default=False,
action="store_true",
help=f"run command and capture console output in {__SCRIPT_NAME__}.svg file",
)
# input options
input_group = parser.add_argument_group("Input ligand-protein complex")
exclusive_input_group = input_group.add_mutually_exclusive_group(required=True)
exclusive_input_group.add_argument(
"--complex",
type=str,
required=False,
metavar="PDB/CIF-FILE",
help="input PDB/CIF file of the ligand-protein complex",
)
exclusive_input_group.add_argument(
"--design-unit",
"--du",
type=str,
metavar="DU-FILE",
help="input design unit file (.oedu)",
)
input_group.add_argument(
"--mol",
type=str,
required=False,
metavar="MOL-FILE",
help="input of molecules at active site (.sdf, .oeb)",
)
# output options
output_group = parser.add_argument_group("Output options")
output_group.add_argument(
"-o",
"--output",
type=str,
required=True,
metavar="OEB/JSON-FILE",
help="output file with perceived interactions (.oeb or .json)",
)
return parser.parse_args()
def main() -> int:
"""Print interactions to console."""
args = parse_options()
console = rich.console.Console(record=args.save_console_svg)
if args.complex:
prot, lig = get_prot_lig_from_pdb(args.complex)
elif args.design_unit:
prot, lig = get_prot_lig_from_design_unit(args.design_unit)
else:
oechem.OEThrow.Fatal("Invalid input option!")
if args.mol:
ifs = oechem.oemolistream()
if not ifs.open(args.mol):
oechem.OEThrow.Fatal(f"Unable to open {args.mol} for reading!")
mols: list[oechem.OEMolBase] = [
oechem.OEGraphMol(m) for m in ifs.GetOEGraphMols()
]
if len(mols) == 0:
oechem.OEThrow.Fatal(f"No molecules read from {args.mol}!")
console.print(
f"{len(mols)} molecule(s) read from '{pathlib.Path(args.mol).name}' file!"
)
ofs = oechem.oemolostream()
if not ofs.open(args.output):
oechem.OEThrow.Fatal(f"Unable to open {args.output} for writing")
if ofs.GetFormat() not in [oechem.OEFormat_OEB, oechem.OEFormat_JSON]:
oechem.OEThrow.Fatal("Output file must have .oeb or .json extension")
if args.mol:
serialize_series_of_interactions(ofs, prot, mols, console)
else:
serialize_interactions(ofs, prot, lig, console)
console.print(
f"Protein and ligand(s) with interactions serialized to '{pathlib.Path(args.output).name}' file"
)
if args.save_console_svg:
console.save_svg(f"{__SCRIPT_NAME__}.svg", title="output")
return os.EX_OK
def serialize_interactions(
ofs: oechem.oemolostream,
protein: oechem.OEMolBase,
ligand: oechem.OEMolBase,
console: rich.console.Console,
) -> bool:
"""Serialize interactions to output file (OEB, JSON)."""
active_site = oechem.OEInteractionHintContainer(protein, ligand)
if not oechem.OEIsValidActiveSite(active_site):
console.print("[red]Invalid active site![/red]")
return False
oechem.OEPerceiveInteractionHints(active_site)
if active_site.NumInteractions() == 0:
console.print("[red]No interactions perceived![/red]")
return False
console.print(f"Number of interactions perceived: {active_site.NumInteractions()}")
protein = oechem.OEGraphMol()
ligand = oechem.OEGraphMol()
if not oechem.OESerializeInteractionsHintContainer(protein, ligand, active_site):
console.print("[red]Failed to serialize interactions![/red]")
return False
oechem.OEWriteMolecule(ofs, protein)
oechem.OEWriteMolecule(ofs, ligand)
return True
def serialize_series_of_interactions(
ofs: oechem.oemolostream,
protein: oechem.OEMolBase,
molecules: list[oechem.OEMolBase],
console: rich.console.Console,
) -> bool:
"""Serialize series of interactions to output file (OEB, JSON)."""
mol_with_interactions: list[oechem.OEMolBase] = []
# set serialization IDs for the protein to ensure that the same protein is recognized across multiple active sites
oechem.OESetInteractionsHintSerializationIds(protein)
with Progress(
TextColumn("{task.description}"),
BarColumn(bar_width=60),
TextColumn("{task.percentage:3.1f}%"),
TimeElapsedColumn(),
transient=False,
disable=len(molecules) < 10, # noqa: PLR2004
console=console,
) as progress:
serialization_task = progress.add_task(
"[blue]Serializing interactions", total=len(molecules)
)
for mol in molecules:
progress.update(serialization_task, advance=1)
active_site = oechem.OEInteractionHintContainer(protein, mol)
if not oechem.OEIsValidActiveSite(active_site):
console.print("[red]Invalid active site![/red]")
continue
oechem.OEPerceiveInteractionHints(active_site)
if active_site.NumInteractions() == 0:
console.print("[red]No interactions perceived![/red]")
continue
ligand = oechem.OEGraphMol()
if not oechem.OESerializeInteractionsHintContainer(
protein, ligand, active_site
):
console.print("[red]Failed to serialize interactions![/red]")
continue
mol_with_interactions.append(oechem.OEGraphMol(ligand))
mol_with_interactions.insert(
0, protein
) # add the protein as the first molecule in the output file
for mol in mol_with_interactions:
oechem.OEWriteMolecule(ofs, mol)
return True
def get_prot_lig_from_pdb(filename: str) -> tuple[oechem.OEMolBase, oechem.OEMolBase]:
"""Initialize active site interaction container from pdb/cif file."""
ifs = oechem.oemolistream()
if not ifs.open(filename):
oechem.OEThrow.Fatal("Unable to open {filename} for reading")
complex_mol = oechem.OEGraphMol()
if not oechem.OEReadMolecule(ifs, complex_mol):
oechem.OEThrow.Fatal("Unable to read complex from {filename}")
if not oechem.OEHasResidues(complex_mol):
oechem.OEPerceiveResidues(complex_mol, oechem.OEPreserveResInfo_All)
# separate ligand and protein
split_opts = oechem.OESplitMolComplexOptions()
ligand = oechem.OEGraphMol()
protein = oechem.OEGraphMol()
water = oechem.OEGraphMol()
other = oechem.OEGraphMol()
split_opts.SetProteinFilter(
oechem.OEOrRoleSet(split_opts.GetProteinFilter(), split_opts.GetWaterFilter())
)
split_opts.SetWaterFilter(
oechem.OEMolComplexFilterFactory(oechem.OEMolComplexFilterCategory_Nothing)
)
oechem.OESplitMolComplex(ligand, protein, water, other, complex_mol, split_opts)
return protein, ligand
def get_prot_lig_from_design_unit(
filename: str,
) -> tuple[oechem.OEMolBase, oechem.OEMolBase]:
"""Initialize active site from design unit."""
du = oechem.OEDesignUnit()
if not oechem.OEIsReadableDesignUnit(filename) or not oechem.OEReadDesignUnit(
filename, du
):
oechem.OEThrow.Fatal("Cannot read design unit.")
protein = oechem.OEGraphMol()
if not du.GetComponents(protein, oechem.OEDesignUnitComponents_TargetComplex):
oechem.OEThrow.Fatal("Could not extract protein from the design unit.")
ligand = oechem.OEGraphMol()
if not du.GetLigand(ligand):
oechem.OEThrow.Fatal("Could not extract ligand from the design unit.")
return protein, ligand
setattr(main, "__SCRIPT_NAME__", __SCRIPT_NAME__)
setattr(main, "__SCRIPT_DESC__", __SCRIPT_DESC__)
setattr(main, "__SCRIPT_TOOLKITS__", __SCRIPT_TOOLKITS__)
setattr(main, "__SCRIPT_KEYWORDS__", __SCRIPT_KEYWORDS__)
setattr(main, "__SCRIPT_CATEGORIES__", __SCRIPT_CATEGORIES__)
if __name__ == "__main__":
sys.exit(main())
Solution
Serialization
The function serialize_interactions shows how interactions stored in the OEInteractionHintContainer class can be serialized to a file with the OESerializeInteractionsHintContainer function. This function adds unique identifiers to atoms involved in interactions, and the fragments involved in interactions from either the ligand or protein are attached to their respective molecules.
These “half”-interactions can then be deserialized and reconstructed with the OEConstructInteractionHintContainer function when the molecules are loaded from the generated file, as shown in get_active_site_from_serialized function.
def serialize_interactions(
ofs: oechem.oemolostream,
protein: oechem.OEMolBase,
ligand: oechem.OEMolBase,
console: rich.console.Console,
) -> bool:
"""Serialize interactions to output file (OEB, JSON)."""
active_site = oechem.OEInteractionHintContainer(protein, ligand)
if not oechem.OEIsValidActiveSite(active_site):
console.print("[red]Invalid active site![/red]")
return False
oechem.OEPerceiveInteractionHints(active_site)
if active_site.NumInteractions() == 0:
console.print("[red]No interactions perceived![/red]")
return False
console.print(f"Number of interactions perceived: {active_site.NumInteractions()}")
protein = oechem.OEGraphMol()
ligand = oechem.OEGraphMol()
if not oechem.OESerializeInteractionsHintContainer(protein, ligand, active_site):
console.print("[red]Failed to serialize interactions![/red]")
return False
oechem.OEWriteMolecule(ofs, protein)
oechem.OEWriteMolecule(ofs, ligand)
return True
The function serialize_series_of_interactions shows how to serialize multiple interactions to the same file, which is useful when you want to perceive interactions for a series of ligands with the same protein. In this case it is important to call the OESetInteractionsHintSerializationIds function for the protein to ensure that the same protein is recognized across multiple active sites during the serialization process.
def serialize_series_of_interactions(
ofs: oechem.oemolostream,
protein: oechem.OEMolBase,
molecules: list[oechem.OEMolBase],
console: rich.console.Console,
) -> bool:
"""Serialize series of interactions to output file (OEB, JSON)."""
mol_with_interactions: list[oechem.OEMolBase] = []
# set serialization IDs for the protein to ensure that the same protein is recognized across multiple active sites
oechem.OESetInteractionsHintSerializationIds(protein)
with Progress(
TextColumn("{task.description}"),
BarColumn(bar_width=60),
TextColumn("{task.percentage:3.1f}%"),
TimeElapsedColumn(),
transient=False,
disable=len(molecules) < 10, # noqa: PLR2004
console=console,
) as progress:
serialization_task = progress.add_task(
"[blue]Serializing interactions", total=len(molecules)
)
for mol in molecules:
progress.update(serialization_task, advance=1)
active_site = oechem.OEInteractionHintContainer(protein, mol)
if not oechem.OEIsValidActiveSite(active_site):
console.print("[red]Invalid active site![/red]")
continue
oechem.OEPerceiveInteractionHints(active_site)
if active_site.NumInteractions() == 0:
console.print("[red]No interactions perceived![/red]")
continue
ligand = oechem.OEGraphMol()
if not oechem.OESerializeInteractionsHintContainer(
protein, ligand, active_site
):
console.print("[red]Failed to serialize interactions![/red]")
continue
mol_with_interactions.append(oechem.OEGraphMol(ligand))
mol_with_interactions.insert(
0, protein
) # add the protein as the first molecule in the output file
for mol in mol_with_interactions:
oechem.OEWriteMolecule(ofs, mol)
return True
Deserialization
The interactions2console script shows how the serialized interactions can be deserialized and reconstructed with the OEConstructInteractionHintContainer function for a protein-ligand complex.
def get_active_site_from_serialized(
filename: str,
) -> oechem.OEInteractionHintContainer:
"""Initialize an active site interaction container from a serialized OEB/JSON file."""
ifs = oechem.oemolistream()
if not ifs.open(filename):
oechem.OEThrow.Fatal(f"Unable to open {filename} for reading")
if ifs.GetFormat() not in [oechem.OEFormat_OEB, oechem.OEFormat_JSON]:
oechem.OEThrow.Fatal("Input file must have .oeb or .json extension")
protein = oechem.OEGraphMol()
ligand = oechem.OEGraphMol()
# protein is expected to be first
if not oechem.OEReadMolecule(ifs, protein) or not oechem.OEReadMolecule(
ifs, ligand
):
oechem.OEThrow.Fatal(f"Unable to read serialized interactions from {filename}")
for mol in [protein, ligand]:
if not oechem.OEHasInteractionsHintSerializationData(mol):
oechem.OEThrow.Fatal("Interaction serialized data is missing!")
active_site = oechem.OEInteractionHintContainer()
if not oechem.OEConstructInteractionHintContainer(active_site, protein, ligand):
oechem.OEThrow.Fatal("Failed to construct active site from serialized data!")
if active_site.NumInteractions() == 0:
oechem.OEThrow.Fatal("No interactions found in the serialized data!")
return active_site
Usage
See Download section to download the script.
> interactions2oeb --help
Perceive interactions of 1GKC.pdb
complex and write them to interactions.oeb.
> interactions2oeb --complex 1GKC.pdb --output interactions.oeb
Perceive interactions of the protein of CDK5.oedu
and a set of molecules of CDK5-hits.sdf
and write them to interactions.oeb.
> interactions2oeb --design-unit CDK5.oedu --mol CDK5-hits.sdf --output interactions.oeb
See also in OEChem TK manual
API
OEConstructInteractionHintContainer function
OEDesignUnit class
OEIsValidActiveSite function
OEPerceiveInteractionHints function
OESplitMolComplex function
OESerializeInteractionsHintContainer function