๐ Convert SMILES to HELM๏
Problem๏
You want to convert a SMILES string to a HELM string.
See also
[Zhang-2012] publication
Ingredients๏
|
Difficulty Level๏
๐ถ๏ธ ๐ถ๏ธ
Download๏
Source Code๏
smiles2helm
#!/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.
"""Convert SMILES string to HELM."""
import argparse
import json
import os
import pathlib
import sys
import rich.console
from openeye import oechem
from rich_argparse import HelpPreviewAction, RichHelpFormatter
__SCRIPT_NAME__ = pathlib.Path(__file__).absolute().stem
__SCRIPT_DESC__ = "Convert SMILES string to HELM."
__SCRIPT_TOOLKITS__ = ["oechem"]
__SCRIPT_KEYWORDS__ = ["HELM", "SMILES", "peptide", "peptide-informatics"]
__SCRIPT_CATEGORIES__ = ["peptide-informatics"]
def parse_options() -> argparse.Namespace:
"""Set up command line options."""
parser = argparse.ArgumentParser(
add_help=True,
formatter_class=RichHelpFormatter,
description="[yellow]" + __SCRIPT_DESC__ + "[/yellow]",
)
input_group = parser.add_argument_group("Input options")
input_group.add_argument(
"--smiles",
metavar="SMILES-STRING",
type=str,
required=True,
help="input SMILES string",
)
monomers_group = parser.add_argument_group("Monomer set options")
_add_monomer_collection(monomers_group)
helm_gen_group = parser.add_argument_group("HELM generation options")
_add_helm_generation_options(helm_gen_group)
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",
)
return parser.parse_args()
def main() -> int:
"""Convert SMILES string to HELM."""
args = parse_options()
console = rich.console.Console(record=args.save_console_svg)
mol = oechem.OEGraphMol()
if not oechem.OESmilesToMol(mol, args.smiles):
console.print("[red]Warning: Invalid SMILES string![/red]")
return os.EX_DATAERR
monomers = _get_monomer_collection(args)
code_set = args.code_set or monomers.GetPrimaryCodeSet()
options = oechem.OEHelmGenerationOptions(code_set)
options.SetAllowUnspecifiedStereo(args.allow_unspecified_stereo)
options.SetAllowUnmatchedFragments(args.allow_unmatched_fragments)
result = oechem.OEHelmGenerationResult()
if not (helm := oechem.OEMolToHelm(mol, monomers, options, result)):
console.print(f"[red]Warning: {result.GetWarning()}[/red]")
else:
console.print(helm, markup=False, highlight=False)
if args.save_console_svg:
console.save_svg(f"{__SCRIPT_NAME__}.svg", title="output")
return os.EX_OK
class MonomerSetParameter: # noqa: PLW1641
"""Utility class to handle both built-in and user defined monomer sets."""
def __init__(self) -> None: # noqa: D107
self._monomer_sets = ["Standard", "OpenEye", "JSON-FILENAME"]
def __repr__(self) -> str: # noqa: D105
return ",".join(self._monomer_sets)
def __eq__(self, param: object) -> bool: # noqa: D105
if not isinstance(param, str):
return False
if param in ["Standard", "OpenEye"]:
return True
console = rich.console.Console()
monomer_set_filepath = pathlib.Path(param)
if (
not monomer_set_filepath.exists()
or monomer_set_filepath.suffix.lower() != ".json"
):
console.print(f"[red]Invalid monomer set file '{param}' ![/red]")
return False
try:
with monomer_set_filepath.open("r") as json_file:
json.load(json_file)
except json.JSONDecodeError as e:
console.print(f"[red]Invalid monomer set file '{param}' ![/red]")
console.print(f"[red]Error decoding JSON: {e} ![/red]")
return False
return True
def _add_monomer_collection(arg_group: argparse._ArgumentGroup) -> None:
arg_group.add_argument(
"-m",
"--monomers",
type=str,
default="Standard",
choices=[MonomerSetParameter()],
help="built-in monomer-set type or json file of monomers",
)
arg_group.add_argument(
"--code-set",
type=str,
metavar="CODE-SET",
required=False,
default=None,
help="code-set, if not specified primary code-set is used",
)
def _get_monomer_collection(args: argparse.Namespace) -> oechem.OEMonomerSet:
monomers = oechem.OEMonomerSet()
match args.monomers:
case "Standard":
oechem.OELoadStandardMonomerSet(monomers)
case "OpenEye":
oechem.OELoadOpenEyeMonomerSet(monomers)
case _:
oechem.OEReadMonomerSet(monomers, args.monomers)
return monomers
def _add_helm_generation_options(arg_group: argparse._ArgumentGroup) -> None:
arg_group.add_argument(
"--allow-unspecified-stereo",
default=False,
action="store_true",
help="allow unspecified stereo in input molecule (default: %(default)s)",
)
arg_group.add_argument(
"--allow-unmatched-fragments",
default=False,
action="store_true",
help="allow unmatched fragments in input molecule -- embedded SMILES in HELM (default: %(default)s)",
)
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๏
The OEHelmToMol function can take a HELM string and a monomer set (OEMonomerSet) and convert it into a molecule representation.
mol = oechem.OEGraphMol()
if not oechem.OESmilesToMol(mol, args.smiles):
console.print("[red]Warning: Invalid SMILES string![/red]")
return os.EX_DATAERR
monomers = _get_monomer_collection(args)
code_set = args.code_set or monomers.GetPrimaryCodeSet()
options = oechem.OEHelmGenerationOptions(code_set)
options.SetAllowUnspecifiedStereo(args.allow_unspecified_stereo)
options.SetAllowUnmatchedFragments(args.allow_unmatched_fragments)
result = oechem.OEHelmGenerationResult()
if not (helm := oechem.OEMolToHelm(mol, monomers, options, result)):
console.print(f"[red]Warning: {result.GetWarning()}[/red]")
else:
console.print(helm, markup=False, highlight=False)
Usage๏
See Download section to download the script.
> smiles2helm --help
By default, the smiles2helm script uses OEChem TKโs
built-in Standard monomer set for the conversion.
> smiles2helm --smiles 'CC[C@H](C)[C@@H](C(=O)N[C@@H](CC(=O)O)C(=O)N[C@@H](CCC(=O)O)C(=O)O)NC(=O)[C@H]([C@@H](C)O)NC(=O)[C@@H]1CCCN1C(=O)[C@H](CCC(=O)O)NC(=O)[C@@H]2CCCN2'
If the sequence generation followed by HELM encoding fails, the smiles2helm will print a warning message.
> smiles2helm --smiles 'CC[C@H](C)[C@@H](C(=O)N[C@@H](CC(=O)O)C(=O)N[C@H](CCC(=O)O)C(=O)O)NC(=O)[C@H]([C@@H](C)O)NC(=O)[C@@H]1CCCN1C(=O)[C@H](CCC(=O)O)NC(=O)[C@@H]2CCCN2'
Monomer Set Options๏
- --monomers OpenEye๏
- --code-set CODE-SET๏
OEChem TKโs built-in OpenEye monomer set can be used with the --monomers OpenEye
parameter.
> smiles2helm --monomers OpenEye --smiles 'CC[C@H](C)[C@@H](C(=O)N[C@@H](CC(=O)O)C(=O)N[C@H](CCC(=O)O)C(=O)O)NC(=O)[C@H]([C@@H](C)O)NC(=O)[C@@H]1CCCN1C(=O)[C@H](CCC(=O)O)NC(=O)[C@@H]2CCCN2'
Since the OpenEye monomer set contains multiple code-sets (OpenEye - default, Standard, PDB, ChEMBL), the
--code-set parameter can be used to generate different versions of the HELM string.
> smiles2helm --monomers OpenEye --code-set ChEMBL --smiles 'CC[C@H](C)[C@@H](C(=O)N[C@@H](CC(=O)O)C(=O)N[C@H](CCC(=O)O)C(=O)O)NC(=O)[C@H]([C@@H](C)O)NC(=O)[C@@H]1CCCN1C(=O)[C@H](CCC(=O)O)NC(=O)[C@@H]2CCCN2'
> smiles2helm --monomers OpenEye --code-set PDB --smiles 'CC[C@H](C)[C@@H](C(=O)N[C@@H](CC(=O)O)C(=O)N[C@H](CCC(=O)O)C(=O)O)NC(=O)[C@H]([C@@H](C)O)NC(=O)[C@@H]1CCCN1C(=O)[C@H](CCC(=O)O)NC(=O)[C@@H]2CCCN2'
- --monomers JSON-MONOMER-FILE๏
The following example shows how to generate a HELM string with a custom monomer set
defined in a json file
(custom-monomers.json)
> smiles2helm --smiles 'C[C@H]1C(=O)N[C@@H](CSSC[C@@H](C(=O)N2CCC[C@H]2C(=O)N[C@H](C(=O)N1)Cc3ccc(cc3)F)N)C(=O)O' --monomers custom-monomers.json
Helm Generation Options๏
- --allow-unspecified-stereo๏
- --allow-unmatched-fragments๏
The success of the sequence generation and HELM encoding process relies heavily on the choice of monomer set and the specific options used during the conversion.
Structure A in Table 1 and Table 2 is an example
where PEPTIDE1{S.P.C}$$$$ can be generated successfully with multiple monomer sets which contain
definition of the 20 standard amino acids.
The sequence of Structure B contains a D-amino acid. The HELM generation will fail when using only the
standard monomers for sequence generation (see Table 1).
In this case HELM can only be generated when the --allow-unmatched-fragments option is turned on that allows
to encode unmapped fragment as an embedded SMILES in the generated HELM.
The OpenEye monomer set can handle D-amino acids and therefore can be used to generate a HELM for this
structure by default (see Table 2).
Generating a HELM for Structure C with unspecified stereo can be achieved by the --allow-unspecified-stereo option.
Since OEChem TK currently does not allow the use of monomers with unspecified atom or bond stereo, input molecules should
have a fully defined stereo configuration in order for monomers to be successfully mapped to them in the sequence generation phase.
By default, any structure with unspecified stereo automatically fails. However, stereo check can be turned off with the
--allow-unspecified-stereo option. Combining this option with the --allow-unmatched-fragments will allow to generate
a HELM where embedded SMILES define the unmapped fragment with unspecified stereo.
Structure A |
Structure B (D-amino acid) |
Structure C (unspecified stereo) |
|
|---|---|---|---|
default options |
PEPTIDE1{S.P.C}$$$$ |
No match for [R1]N1CCC[C@@H]1C(=O)[R2]! |
Unspecified atom stereo in molecule |
handle stereo [1] |
PEPTIDE1{S.P.C}$$$$ |
No match for [R1]N1CCC[C@@H]1C(=O)[R2]! |
No match for [R1]N1CCCC1C(=O)[R2]! |
handle stereo and
unmatched [2]
|
PEPTIDE1{S.P.C}$$$$ |
PEPTIDE1{S.[[R1]N1CCC[C@@H]1C(=O)[R2]].C}$$$$ |
PEPTIDE1{S.[[R1]N1CCCC1C(=O)[R2]].C}$$$$ |
Structure A |
Structure B (D-amino acid) |
Structure C (unspecified stereo) |
|
default options [3] |
PEPTIDE1{S.P.C}$$$$ |
PEPTIDE1{S.[dPro].C}$$$$ |
Unspecified atom stereo in molecule |
handle stereo [4] |
PEPTIDE1{S.P.C}$$$$ |
PEPTIDE1{S.[dPro].C}$$$$ |
No match for [R1]N1CCCC1C(=O)[R2]! |
handle stereo and
unmatched [5]
|
PEPTIDE1{S.P.C}$$$$ |
PEPTIDE1{S.[dPro].C}$$$$ |
PEPTIDE1{S.[[R1]N1CCCC1C(=O)[R2]].C}$$$$ |
โmonomers OpenEye
โmonomers OpenEye โallow-unspecified-stereo
โmonomers OpenEye โallow-unspecified-stereo โallow-unmatched-fragments
See also in OEChem TK manual๏
API
OEMonomerSet class
OEReadMonomerSet, OELoadStandardMonomerSet, and OELoadOpenEyeMonomerSet functions
OEHelmGenerationOptions class
OEHelmGenerationResult class
OEHelmGenerationReturnCode namespace
OEMolToHelm function