Search PLINDER with your structures and sequences#
search() finds similar proteins, ligand pockets and protein interfaces in PLINDER. Supply an input table for ligand-containing structures, or a FASTA file or structure folder for ligand-free queries.
Input requirements#
Search and evaluation share a table with input_id, structure_path, and optional ligand_path columns. Evaluation adds reference_id. See the input table reference for details.
Use an assembled PDB/mmCIF receptor with one or more ligand SDFs, or a complete mmCIF containing the ligand coordinates and chemistry. The SDFs must use the receptor’s coordinate frame.
For ligand-free queries, pass a FASTA file, a structure file, or a folder of PDB/mmCIF structures directly.
import os
from pathlib import Path
import pandas as pd
from plinder.core import PlinderSystem, PlinderRelease
from plinder.core.scores import (
CustomProteinSearchConfig,
search,
)
release_path = os.environ.get("PLINDER_DATA_DIR")
release_path = Path(release_path) if release_path else None
input_cif = (
Path(os.environ["PLINDER_CUSTOM_CIF"])
if "PLINDER_CUSTOM_CIF" in os.environ
else PlinderSystem(
system_id="4agi__1__1.C__1.W",
release=PlinderRelease(data_dir=release_path),
).receptor_cif
)
work_dir = Path(
os.environ.get("PLINDER_CUSTOM_WORK_DIR", "custom_scoring_example")
)
/home/runner/micromamba/envs/plinder/lib/python3.12/site-packages/vcs_versioning/_backends/_git.py:709: UserWarning: "/home/runner/work/plinder/plinder" is shallow and may cause errors
pre_parse(wd)
Run a small receptor-only example#
The example rebuilds a PLINDER receptor mmCIF when PLINDER_CUSTOM_CIF is not set. plinder_entry_ids limits the comparison to receptor and interface proteins from the selected entries; omit it to search all PLINDER proteins.
mode="pockets" searches protein and pocket features. Use mode="interfaces" for protein interfaces, mode="ligands" for ligand comparisons, or mode="both" for both. The default mode="auto" selects applicable features.
%%capture
result = search(
input_cif,
output_dir=work_dir,
release=PlinderRelease(data_dir=release_path),
mode="pockets",
backends=("mmseqs",),
plinder_entry_ids=["4agi"],
search_config=CustomProteinSearchConfig(max_seqs=25),
threads=2,
store_aligned_pocket_residues=True,
)
createdb /home/runner/work/plinder/plinder/docs/examples/custom_scoring_example/query_inputs/query_sequences.fasta /home/runner/work/plinder/plinder/docs/examples/custom_scoring_example/query_databases/mmseqs --threads 2
MMseqs Version: 18.8cc5c
Database type 0
Shuffle input database true
Createdb mode 0
Write lookup file 1
Offset of numeric ids 0
Threads 2
Compressed 0
Mask residues 0
Mask residues probability 0.9
Mask lower case residues 0
Mask lower letter repeating N times 0
Use GPU 0
Verbosity 3
Converting sequences
[
Time for merging to mmseqs_h: 0h 0m 0s 0ms
Time for merging to mmseqs: 0h 0m 0s 0ms
Database type: Aminoacid
Time for processing: 0h 0m 0s 4ms
createdb /home/runner/work/plinder/plinder/docs/examples/custom_scoring_example/plinder_target_subset/mmseqs/targets.fasta /home/runner/work/plinder/plinder/docs/examples/custom_scoring_example/plinder_target_subset/mmseqs/targets --threads 2
MMseqs Version: 18.8cc5c
Database type 0
Shuffle input database true
Createdb mode 0
Write lookup file 1
Offset of numeric ids 0
Threads 2
Compressed 0
Mask residues 0
Mask residues probability 0.9
Mask lower case residues 0
Mask lower letter repeating N times 0
Use GPU 0
Verbosity 3
Converting sequences
[
Time for merging to targets_h: 0h 0m 0s 0ms
Time for merging to targets: 0h 0m 0s 0ms
Database type: Aminoacid
Time for processing: 0h 0m 0s 4ms
protein_scores = pd.read_parquet(result.protein_scores)
residue_pairs = pd.read_parquet(result.aligned_pocket_residues)
{
"protein_score_rows": len(protein_scores),
"aligned_pocket_residue_rows": len(residue_pairs),
"ligand_scores_written": result.ligand_scores is not None,
"interface_scores_written": result.interface_scores is not None,
}
{'protein_score_rows': 442,
'aligned_pocket_residue_rows': 339,
'ligand_scores_written': False,
'interface_scores_written': False}
Protein scoring is oriented from each PLINDER receptor and ligand pocket to a custom protein chain. pocket_fident is therefore the percentage of PLINDER pocket residues that align to identical residues in the custom chain.
pocket_scores = protein_scores.loc[
protein_scores["metric"].astype(str).eq("pocket_fident"),
[
"query_system",
"query_ligand_id",
"target_system",
"protein_mapping",
"source",
"similarity",
],
].head()
pocket_scores
| query_system | query_ligand_id | target_system | protein_mapping | source | similarity | |
|---|---|---|---|---|---|---|
| 15 | 4agi__1__1.C__1.R | 4agi__1__1.R | receptor_1.C | 1.C:0.1.C | mmseqs | 100 |
| 32 | 4agi__1__1.C__1.S | 4agi__1__1.S | receptor_1.C | 1.C:0.1.C | mmseqs | 100 |
| 49 | 4agi__1__1.C__1.T | 4agi__1__1.T | receptor_1.C | 1.C:0.1.C | mmseqs | 100 |
| 66 | 4agi__1__1.C__1.U | 4agi__1__1.U | receptor_1.C | 1.C:0.1.C | mmseqs | 100 |
| 83 | 4agi__1__1.C__1.V | 4agi__1__1.V | receptor_1.C | 1.C:0.1.C | mmseqs | 100 |
When aligned pocket residues are requested, each row records a PLINDER pocket residue and the custom residue selected by the chain mapping and search source used for the reported score.
residue_pairs[[
"plinder_system_id",
"plinder_chain_instance",
"plinder_residue_number",
"custom_structure_id",
"custom_chain_asym_id",
"custom_residue_number",
"residue_identical",
]].head()
| plinder_system_id | plinder_chain_instance | plinder_residue_number | custom_structure_id | custom_chain_asym_id | custom_residue_number | residue_identical | |
|---|---|---|---|---|---|---|---|
| 0 | 4agi__1__1.C__1.R | 1.C | 25 | receptor | 1.C | 25 | True |
| 1 | 4agi__1__1.C__1.R | 1.C | 27 | receptor | 1.C | 27 | True |
| 2 | 4agi__1__1.C__1.R | 1.C | 37 | receptor | 1.C | 37 | True |
| 3 | 4agi__1__1.C__1.R | 1.C | 39 | receptor | 1.C | 39 | True |
| 4 | 4agi__1__1.C__1.R | 1.C | 41 | receptor | 1.C | 41 | True |
Search ligand poses#
Each SDF supplies its own ligand chemistry and coordinates. Put the receptor path and ligand path in the same input row, and repeat the input ID for additional ligands.
def search_ligand_model(receptor_path, ligand_path):
inputs = pd.DataFrame([{
"input_id": "model_1",
"structure_path": str(receptor_path),
"ligand_path": str(ligand_path),
}])
return search(
inputs, output_dir="ligand_search", mode="ligands",
)
# Examples:
# search_ligand_model("receptor.pdb", "pose.sdf")
Compare protein sequences#
Protein sequences can be searched without coordinates. This MMseqs route writes protein scores, ligand-pocket links, the best link per input sequence, and optionally the aligned pocket residues.
def search_sequences(fasta_path):
return search(
Path(fasta_path),
output_dir=Path(fasta_path).with_suffix("") / "plinder_search",
threads=8,
store_aligned_pocket_residues=True,
)
For several ligand-free coordinate files, use search("models/", output_dir="search_results"). Search uses the first coordinate model as the supplied assembly.