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.