🆕 Generate Random Peptides
Problem
You want to generate random peptides matching a specific HELM pattern. If you want to mutate specific positions in a peptide, see 🆕 Mutate Peptide section.
Ingredients
|
Difficulty Level
🌶️ 🌶️
Solution
The random_peptides script generates random peptides from a
user-specified HELM pattern. Positions in the pattern that contain
placeholder monomer codes (e.g. X1, X2, X3) are randomly
substituted with valid monomers from the selected monomer set.
The script outputs the generated peptides as HELM strings and/or
molecular structures.
The core of the random_peptides script is the get_next_random_peptide generator function, which produces random peptides one at a time. For each peptide, it randomly selects a monomer code for every placeholder position in the HELM pattern and attempts to construct the molecule. Invalid combinations are silently skipped, ensuring that only chemically valid peptides are yielded.
def get_next_random_peptide(
helm_pattern: str,
num_peptides: int,
replace_code_dict: dict[str, list[str]],
monomers: oechem.OEMonomerSet,
rand: random.Random,
) -> Iterator[tuple[oechem.OEMolBase, str]]:
"""Generate random peptides as OEGraphMol and HELM string tuples."""
mol = oechem.OEGraphMol()
while num_peptides > 0:
helm = helm_pattern
for replace_code, equivalent_codes in replace_code_dict.items():
random_code = equivalent_codes[rand.randint(0, len(equivalent_codes) - 1)]
helm = helm.replace(
f"[{replace_code}]",
f"[{random_code}]" if len(random_code) > 1 else random_code,
)
if not oechem.OEHelmToMol(mol, helm, monomers):
# skip invalid HELM patterns that cannot be converted to molecules with the given monomer set
continue
num_peptides -= 1
yield (oechem.OEGraphMol(mol), helm)
Download
Source Code
random_peptides
#!/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.
"""Generate random peptides (HELM or/and structure)."""
import argparse
import contextlib
import json
import os
import pathlib
import random
import sys
from collections.abc import Iterator
import rich.console
from openeye import oechem
from rich.markdown import Markdown
from rich.progress import BarColumn, Progress, TextColumn
from rich_argparse import HelpPreviewAction, RichHelpFormatter
__SCRIPT_NAME__ = pathlib.Path(__file__).absolute().stem
__SCRIPT_DESC__ = "Generate random peptides."
__SCRIPT_TOOLKITS__ = ["oechem"]
__SCRIPT_KEYWORDS__ = ["HELM", "monomer", "peptide", "peptide-informatics", "random"]
__SCRIPT_CATEGORIES__ = ["peptide-informatics"]
MONOMER_SET_STANDARD = "Standard"
MONOMER_SET_OPENEYE = "OpenEye"
MONOMER_SET_JSON_FILENAME = "JSON-FILENAME"
__PATTERN_EXAMPLES__ = """
----------------------------
Examples:
- PEPTIDE1{[X1].A.[X3]}$$$$ - to generate random tri-peptides with Alanine in the middle
- PEPTIDE1{[X1].[X2].[X3].[X4].[X5]}$$$$ - to generate random palindrome penta-peptides
- PEPTIDE1{[X1].[X2].[X3].[X4].[X5]}$PEPTIDE1,PEPTIDE1,1:R1-5:R2$$$ - generate random cyclic penta-peptides
- PEPTIDE1{[X1].C.[X3].[X4].[X5].C.[X7]}$PEPTIDE1,PEPTIDE1,2:R3-6:R3$$$ - generate random disulfide bridged hepta-peptides
"""
def parse_options() -> argparse.Namespace:
"""Parse command-line options for random peptide generation."""
parser = argparse.ArgumentParser(
add_help=True,
formatter_class=RichHelpFormatter,
description="[yellow]" + __SCRIPT_DESC__ + "[/yellow]",
epilog=Markdown(__PATTERN_EXAMPLES__), # type: ignore # noqa: PGH003
)
io_group = parser.add_argument_group("Output options")
io_group.add_argument(
"--helm",
metavar="HELM-FILE",
type=str,
required=False,
help="output file of HELM string",
)
io_group.add_argument(
"--mol",
metavar="MOL-FILE",
type=str,
required=False,
help="output molecule file",
)
generation_group = parser.add_argument_group("Peptide generation options")
generation_group.add_argument(
"--helm-pattern",
metavar="HELM-PATTERN",
default="PEPTIDE1{[X1].[X2].[X3]}$$$$",
type=str,
required=False,
help="HELM pattern to generate peptides (default: %(default)s)",
)
generation_group.add_argument(
"--num-peptides",
metavar="N",
default=10,
type=int,
required=False,
help="number of peptides to generate (default: %(default)s)",
)
generation_group.add_argument(
"--random-seed",
metavar="SEED",
type=int,
required=False,
help="random seed for peptide generation",
)
monomers_group = parser.add_argument_group("Monomer set options")
_add_monomer_collection(monomers_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:
"""Generate random peptides and write requested outputs."""
args = parse_options()
console = rich.console.Console(record=args.save_console_svg)
if args.helm is None and args.mol is None:
console.print(
"[red]Error: No output file specified, use --helm or --mol.[/red]"
)
return os.EX_USAGE
monomers = _get_monomer_collection(args)
code_set = monomers.GetPrimaryCodeSet() if args.code_set is None else args.code_set
mol_file: None | oechem.oemolostream = None
if args.mol is not None:
mol_file = oechem.oemolostream(args.mol)
helm_pattern = args.helm_pattern
if not (
replace_codes := _validate_helm_pattern(
helm_pattern, monomers, code_set, console
)
):
return os.EX_DATAERR
num_peptides = args.num_peptides
replace_code_dict: dict[str, list[str]] = {}
for code in replace_codes:
equivalent_codes = _get_equivalent_monomer_codes(
code, helm_pattern, monomers, code_set, replace_codes
)
console.print(
f"[bold blue][{code:2s}][/bold blue] can be randomly replaced with {len(equivalent_codes)} monomers"
)
replace_code_dict[code] = equivalent_codes
rand = random.Random(args.random_seed) # noqa: S311
with (
Progress(
TextColumn("{task.description}"),
BarColumn(bar_width=60),
TextColumn("{task.percentage:3.1f}%"),
transient=True,
disable=num_peptides < 10, # noqa: PLR2004
console=console,
) as progress,
(
pathlib.Path(args.helm).open("w")
if args.helm is not None
else contextlib.nullcontext()
) as helm_file,
):
conversion = progress.add_task("[blue]HELM conversion", total=num_peptides)
for mol, helm in get_next_random_peptide(
helm_pattern, num_peptides, replace_code_dict, monomers, rand
):
if helm_file is not None:
helm_file.write(helm + "\n")
if mol_file is not None:
oechem.OEWriteMolecule(mol_file, mol)
progress.update(conversion, advance=1)
if args.save_console_svg:
console.save_svg(f"{__SCRIPT_NAME__}.svg", title="output")
return os.EX_OK
def get_next_random_peptide(
helm_pattern: str,
num_peptides: int,
replace_code_dict: dict[str, list[str]],
monomers: oechem.OEMonomerSet,
rand: random.Random,
) -> Iterator[tuple[oechem.OEMolBase, str]]:
"""Generate random peptides as OEGraphMol and HELM string tuples."""
mol = oechem.OEGraphMol()
while num_peptides > 0:
helm = helm_pattern
for replace_code, equivalent_codes in replace_code_dict.items():
random_code = equivalent_codes[rand.randint(0, len(equivalent_codes) - 1)]
helm = helm.replace(
f"[{replace_code}]",
f"[{random_code}]" if len(random_code) > 1 else random_code,
)
if not oechem.OEHelmToMol(mol, helm, monomers):
# skip invalid HELM patterns that cannot be converted to molecules with the given monomer set
continue
num_peptides -= 1
yield (oechem.OEGraphMol(mol), helm)
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 = [
MONOMER_SET_STANDARD,
MONOMER_SET_OPENEYE,
MONOMER_SET_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 [MONOMER_SET_STANDARD, MONOMER_SET_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
is_valid = True
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]")
is_valid = False
return is_valid
def _add_monomer_collection(arg_group: argparse._ArgumentGroup) -> None:
arg_group.add_argument(
"-m",
"--monomers",
type=str,
default=MONOMER_SET_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 _validate_helm_pattern(
helm_pattern: str,
monomers: oechem.OEMonomerSet,
code_set: str,
console: rich.console.Console,
) -> None | list[str]:
if oechem.OEGetHelmChainNames(helm_pattern) != ["PEPTIDE1"]:
console.print(
f"[red]Invalid HELM pattern: {helm_pattern}; it should only have the PEPTIDE1 chain![/red]"
)
return None
codes = set(oechem.OEGetHelmMonomerCodes(helm_pattern, "PEPTIDE1"))
replace_codes: list[str] = [
code for code in codes if monomers.GetMonomer(code_set, code) is None
]
replace_codes.sort()
console.print(
f"Randomly replace monomer codes [blue]{replace_codes}[/blue] in `{helm_pattern}`"
)
# generate "fake" monomers to test helm_pattern
for repeat, code in enumerate(replace_codes, start=1):
fake_monomer_smiles = "[H:1]N[C@@H](C" + repeat * "[U]" + "S[H:3])C(=O)[OH:2]"
monomer_data = oechem.OEMonomerData(fake_monomer_smiles)
monomer_data.AddCode(code_set, code)
if not oechem.OEIsValidMonomerData(monomer_data):
return None
monomers.AddMonomer(monomer_data)
mol = oechem.OEGraphMol()
result = oechem.OEHelmParsingResult()
if not oechem.OEHelmToMol(mol, helm_pattern, monomers, result):
console.print(
f"[red]Invalid HELM pattern: {helm_pattern} : {result.GetWarning()}![/red]"
)
return None
return replace_codes
def _get_equivalent_monomer_codes(
replace_code: str,
helm_pattern: str,
monomers: oechem.OEMonomerSet,
code_set: str,
ignore_codes: list[str],
) -> list[str]:
equivalent_monomer_codes = []
mol = oechem.OEGraphMol()
for monomer in monomers.GetMonomers(oechem.OEIsInMonomerCodeSet(code_set)):
code = monomer.GetCode(code_set)
if code in ignore_codes:
continue
if monomer.GetPolymerType() != oechem.OEPolymerType_Peptide:
continue
test_helm = helm_pattern.replace(
f"[{replace_code}]", f"[{code}]" if len(code) > 1 else code
)
if oechem.OEHelmToMol(mol, test_helm, monomers):
equivalent_monomer_codes.append(code)
return equivalent_monomer_codes
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())
Usage
See Download section to download the script.
> random_peptides --help
By default, the random_peptides script uses OEChem TK’s
built-in Standard monomer set and generates 10 random tri-peptides
matching the default PEPTIDE1{[X1].[X2].[X3]}$$$$ pattern.
> random_peptides --helm peptides.helm --mol peptides.ism
The random_peptides script reports how many monomers can be used randomly at each position in the pattern. When using the default options, each position can be substituted with any of the 20 standard amino acids.
The command above will generate peptides.helm
PEPTIDE1{F.W.D}$$$$
PEPTIDE1{K.E.S}$$$$
PEPTIDE1{R.S.P}$$$$
PEPTIDE1{H.E.S}$$$$
PEPTIDE1{A.P.Q}$$$$
PEPTIDE1{Y.A.R}$$$$
PEPTIDE1{K.I.W}$$$$
PEPTIDE1{E.M.A}$$$$
PEPTIDE1{A.A.V}$$$$
PEPTIDE1{A.P.H}$$$$
and peptides.ism
c1ccc(cc1)C[C@@H](C(=O)N[C@@H](Cc2c[nH]c3c2cccc3)C(=O)N[C@@H](CC(=O)O)C(=O)O)N
C(CCN)C[C@@H](C(=O)N[C@@H](CCC(=O)O)C(=O)N[C@@H](CO)C(=O)O)N
C1C[C@H](N(C1)C(=O)[C@H](CO)NC(=O)[C@H](CCCNC(=N)N)N)C(=O)O
c1c([nH]cn1)C[C@@H](C(=O)N[C@@H](CCC(=O)O)C(=O)N[C@@H](CO)C(=O)O)N
C[C@@H](C(=O)N1CCC[C@H]1C(=O)N[C@@H](CCC(=O)N)C(=O)O)N
C[C@@H](C(=O)N[C@@H](CCCNC(=N)N)C(=O)O)NC(=O)[C@H](Cc1ccc(cc1)O)N
CC[C@H](C)[C@@H](C(=O)N[C@@H](Cc1c[nH]c2c1cccc2)C(=O)O)NC(=O)[C@H](CCCCN)N
C[C@@H](C(=O)O)NC(=O)[C@H](CCSC)NC(=O)[C@H](CCC(=O)O)N
C[C@@H](C(=O)N[C@@H](C)C(=O)N[C@@H](C(C)C)C(=O)O)N
C[C@@H](C(=O)N1CCC[C@H]1C(=O)N[C@@H](Cc2cnc[nH]2)C(=O)O)N
Output Options
- --helm HELM-FILE
- --mol MOL-FILE
At least one of these options must be specified to generate output peptides.
Peptide Generation Options
- --helm-pattern HELM-PATTERN
The default HELM pattern for random peptide generation is PEPTIDE1{[X1].[X2].[X3]}$$$$ which
generates random linear tri-peptides.
In the HELM pattern, each monomer code, such as X1, that does not exist in the monomer set will be
replaced with a random valid amino acid code. The generated HELM is then converted to a molecule for
validation.
The following examples show how to use the --helm-pattern option to generate
random peptides with various topologies.
If a monomer code is valid in the HELM pattern, then it will remain fixed
in the generated peptides.
For example, when using PEPTIDE1{A.[X2].[X3]}$$$$ every peptide will have a
N-terminal Alanine, while X2 and X3 will be replaced with random monomer symbols.
> random_peptides --helm peptides.helm --mol peptides.ism --helm-pattern 'PEPTIDE1{A.[X2].[X3]}$$$$'
generates peptides.helm
and peptides.ism
If a monomer code such as X1 occurs multiple times in the HELM pattern, then it will be
replaced with the same random monomer.
For example, PEPTIDE1{[X1].[X2].[X3].[X2].[X1]}$$$$ will generate random palindrome penta-peptides.
> random_peptides --helm peptides.helm --mol peptides.ism --helm-pattern 'PEPTIDE1{[X1].[X2].[X3].[X2].[X1]}$$$$'
generates peptides.helm
and peptides.ism
Cyclic peptides can be generated with specifying the ring-closing connection in the HELM pattern.
In the example below, PEPTIDE1{[X1].[X2].[X3].[X4].[X5]}$PEPTIDE1,PEPTIDE1,1:R1-5:R2$$$ defines
a cyclic penta-peptide where the first and last monomers are connected by a peptide bond.
> random_peptides --helm peptides.helm --mol peptides.ism --helm-pattern 'PEPTIDE1{[X1].[X2].[X3].[X4].[X5]}$PEPTIDE1,PEPTIDE1,1:R1-5:R2$$$'
generates peptides.helm
and peptides.ism
Cyclic peptides with cross-links can be generated by specifying extra connections in the HELM pattern.
The PEPTIDE1{[X1].C.[X3].C.C.[X6].[X7].C.[X8].[X9].C.[X11]}$PEPTIDE1,PEPTIDE1,4:R3-11:R3|PEPTIDE1,PEPTIDE1,8:R3-5:R3$$$
example defines two disulfide bonds between cysteine monomers using R3 side-chain connections to generate peptides with
topology similar to:
> random_peptides --helm peptides.helm --mol peptides.ism --helm-pattern 'PEPTIDE1{[X1].C.[X3].C.C.[X6].[X7].C.[X8].[X9].C.[X11]}$PEPTIDE1,PEPTIDE1,4:R3-11:R3|PEPTIDE1,PEPTIDE1,8:R3-5:R3$$$'
generates peptides.helm
and peptides.ism
- --num-peptides N
The --num-peptides option can be used to specify the number of random peptides
to generate (default: 10).
- --random-seed SEED
The --random-seed can be used to make random peptide generation reproducible by
setting a specific seed value.
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.
> random_peptides --helm peptides.helm --mol peptides.ism --monomers OpenEye
will generate peptides.helm
and peptides.ism
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.
> random_peptides --helm peptides.helm --mol peptides.ism --monomers OpenEye --code-set ChEMBL
will generate peptides.helm
and peptides.ism
- --monomers JSON-MONOMER-FILE
The following example shows how to use custom monomers
defined in a json file
(custom-monomers.json)
> random_peptides --helm peptides.helm --mol peptides.ism --monomers custom-monomers.json
will generate peptides.helm
and peptides.ism
See also in OEChem TK manual
API
OEMonomerSet class
OEReadMonomerSet, OELoadStandardMonomerSet, and OELoadOpenEyeMonomerSet functions
OEHelmGenerationOptions class
OEHelmGenerationResult class
OEHelmGenerationReturnCode namespace
OEMolToHelm function