diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc9d03a..5716596 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,9 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.1.0] - 2022-11-XX
+
### Added
- `Fingerprint.run` now has a `converter_kwargs` parameter that can pass kwargs to the
underlying RDKitConverter from MDAnalysis (Issue #57).
+- Formatting with `black`.
### Changed
- The SMARTS for the following groups have been updated to a more accurate definition
@@ -23,61 +25,78 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Cation: include amidine and guanidine,
- Metal ligand: exclude amides and some amines.
- The Pi stacking interactions have been changed for a more accurate implementation
- (PR #97).
+ (PR #97, PR #98).
+- The Van der Waals contact has been added to the default interactions, and the `tolerance`
+ parameter has been set to 0.
- The `pdbqt_supplier` will not add explicit hydrogen atoms anymore, to avoid detecting
- hydrogen bonds with "random" hydrogens that weren't in the PDBQT file.
-- When using the `pdbqt_supplier`, irrelevant warnings and logs have been disabled.
-- Updated the minimal RDKit version to `2021.03.1`
+ hydrogen bonds with "random" hydrogens that weren't in the PDBQT file (PR #99).
+- When using the `pdbqt_supplier`, irrelevant warnings and logs have been disabled (PR #99).
+- Updated the minimal RDKit version to `2021.03.1`
### Fixed
- Dead link in the quickstart notebook for the MDAnalysis quickstart (PR #75, @radifar).
-- The `pdbqt_supplier` now correctly preserves hydrogens from the input PDBQT file.
+- The `pdbqt_supplier` now correctly preserves hydrogens from the input PDBQT file (PR #99).
+- If no interaction was detected, `to_dataframe` would error without giving a helpful message. It
+ now returns a dataframe with the correct number of frames in the index and no column.
+
## [1.0.0] - 2022-06-07
+
### Added
- Support for multiprocessing, enabled by default (Issue #46). The number of processes can
be controlled through `n_jobs` in `fp.run` and `fp.run_from_iterable`.
- New interaction: van der Waals contact, based on the sum of vdW radii of two atoms.
- Saving/loading the fingerprint object as a pickle with `fp.to_pickle` and
`Fingerprint.from_pickle` (Issue #40).
+
### Changed
- Molecule suppliers can now be indexed, reused and can return their length, instead of
being single-use generators.
+
### Fixed
- ProLIF can now be installed through pip and conda (Issue #6).
- If no interaction is detected in the first frame, `to_dataframe` will not complain about
a `KeyError` anymore (Issue #44).
- When creating a `plf.Fingerprint`, unknown interactions will no longer fail silently.
+
## [0.3.4] - 2021-09-28
+
### Added
- Added our J. Cheminformatics article to the citation page of the documentation and the
`CITATION.cff` file.
+
### Changed
- Improved the documentation on how to properly restrict interactions to ignore the
protein backbone (Issue #22), how to fix the empty dataframe issue when no bond
information is present in the PDB file (Issue #15), how to save the LigNetwork diagram
(Issue #21), and some clarifications on using `fp.generate`
+
### Fixed
- Mixing residue type with interaction type in the interactive legend of the LigNetwork
would incorrectly display/hide some residues on the canvas (#PR 23)
- MOL2 files starting with a comment (`#`) would lead to an error
+
## [0.3.3] - 2021-06-11
+
### Changed
- Custom interactions must return three values: a boolean for the interaction,
and the indices of residue atoms responsible for the interaction
+
### Fixed
- Custom interactions that only returned a single value instead of three would
raise an uninformative error message
## [0.3.2] - 2021-06-11
+
### Added
- LigNetwork: an interaction diagram with atomistic details for the ligand and
residue-level details for the protein, fully interactive in a browser/notebook, inspired
from LigPlot (PR #19)
- `fp.generate`: a method to get the IFP between two `prolif.Molecule` objects (PR #19)
+
### Changed
- Default residue name and number: `UNK` and `0` are now the default values if `None` or
`''` is given
@@ -87,11 +106,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
recalculating the IFP if one wants to display it with atomic details (PR #19)
- Changed the values returned by `fp.bitvector_atoms`: the atom indices have been
separated in two lists, one for the ligand and one for the protein (PR #19)
+
### Fixed
- Residues with a resnumber of `0` are not converted to `None` anymore (Issue #13)
- Fingerprint instantiated with an unknown interaction name will now raise a `NameError`
+
## [0.3.1] - 2021-02-02
+
### Added
- Integration with Zenodo to automatically generate a DOI for new releases
- Citation page
@@ -99,6 +121,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- PDBQT, MOL2 and SDF molecule suppliers to make it easier for users to use docking
results as input (Issue #11)
- `Molecule.from_rdkit` classmethod to easily prepare RDKit molecules for ProLIF
+
### Changed
- The visualisation notebook now displays the protein with py3Dmol. Some examples for
creating and displaying a graph from the interaction dataframe have been added
@@ -109,25 +132,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added the `Fingerprint.run_from_iterable` method, which uses the new supplier functions
to quickly generate a fingerprint.
- Sorted the output of `Fingerprint.list_available`
+
### Fixed
- `Fingerprint.to_dataframe` is now much faster (Issue #7)
- `ResidueId.from_string` method now supports 1-letter and 2-letter codes for RNA/DNA
(Issue #8)
+
## [0.3.0] - 2020-12-23
+
### Added
- Reading input directly from RDKit Mol as well as MDAnalysis AtomGroup objects
- Proper documentation and tests
- CI through GitHub Actions
- Publishing to PyPI triggered by GitHub releases
+
### Changed
- All the API and the underlying code have been modified
- Repository has been moved from GitHub user @cbouy to organisation @chemosim-lab
+
### Removed
- Custom MOL2 file reader
- Command-line interface
+
### Fixed
- Interactions not detected properly
+
## [0.2.1] - 2019-10-02
+
Base version for this changelog
\ No newline at end of file
diff --git a/docs/notebooks/how-to.ipynb b/docs/notebooks/how-to.ipynb
index fa0d52b..f1ce20b 100644
--- a/docs/notebooks/how-to.ipynb
+++ b/docs/notebooks/how-to.ipynb
@@ -139,6 +139,7 @@
"class Hydrophobic(plf.interactions.Hydrophobic):\n",
" pass\n",
"\n",
+ "\n",
"fp = plf.Fingerprint([\"Hydrophobic\"])\n",
"fp.hydrophobic(lmol, pmol[\"TYR109.A\"])"
]
@@ -173,7 +174,8 @@
"class CustomHydrophobic(plf.interactions.Hydrophobic):\n",
" def __init__(self):\n",
" super().__init__(distance=4.0)\n",
- " \n",
+ "\n",
+ "\n",
"fp = plf.Fingerprint([\"Hydrophobic\", \"CustomHydrophobic\"])\n",
"fp.hydrophobic(lmol, pmol[\"TYR109.A\"])"
]
@@ -247,6 +249,7 @@
"source": [
"from scipy.spatial import distance_matrix\n",
"\n",
+ "\n",
"class CloseContact(plf.interactions.Interaction):\n",
" def __init__(self, threshold=2.0):\n",
" self.threshold = threshold\n",
@@ -260,6 +263,7 @@
" return True, res1_i[0], res2_i[0]\n",
" return False, None, None\n",
"\n",
+ "\n",
"fp = plf.Fingerprint([\"CloseContact\"])\n",
"fp.closecontact(lmol, pmol[\"ASP129.A\"])"
]
@@ -293,8 +297,7 @@
"source": [
"ifp = fp.generate(lmol, pmol, return_atoms=True)\n",
"# check the interactino between the ligand and ASP129\n",
- "ifp[(plf.ResidueId(\"LIG\", 1, \"G\"),\n",
- " plf.ResidueId(\"ASP\", 129, \"A\"))]"
+ "ifp[(plf.ResidueId(\"LIG\", 1, \"G\"), plf.ResidueId(\"ASP\", 129, \"A\"))]"
]
},
{
@@ -424,10 +427,13 @@
"df0.columns = df0.columns.droplevel(0)\n",
"df.columns = df.columns.droplevel(0)\n",
"# concatenate and sort columns\n",
- "df = (pd.concat([df0, df])\n",
- " .fillna(False)\n",
- " .sort_index(axis=1, level=0,\n",
- " key=lambda index: [plf.ResidueId.from_string(x) for x in index]))\n",
+ "df = (\n",
+ " pd.concat([df0, df])\n",
+ " .fillna(False)\n",
+ " .sort_index(\n",
+ " axis=1, level=0, key=lambda index: [plf.ResidueId.from_string(x) for x in index]\n",
+ " )\n",
+ ")\n",
"df"
]
},
@@ -593,7 +599,9 @@
"source": [
"from rdkit import Chem\n",
"\n",
- "template = Chem.MolFromSmiles(\"C[NH+]1CC(C(=O)NC2(C)OC3(O)C4CCCN4C(=O)C(Cc4ccccc4)N3C2=O)C=C2c3cccc4[nH]cc(c34)CC21\")\n",
+ "template = Chem.MolFromSmiles(\n",
+ " \"C[NH+]1CC(C(=O)NC2(C)OC3(O)C4CCCN4C(=O)C(Cc4ccccc4)N3C2=O)C=C2c3cccc4[nH]cc(c34)CC21\"\n",
+ ")\n",
"template"
]
},
diff --git a/docs/notebooks/protein-protein_interactions.ipynb b/docs/notebooks/protein-protein_interactions.ipynb
index 3955384..e4b4177 100644
--- a/docs/notebooks/protein-protein_interactions.ipynb
+++ b/docs/notebooks/protein-protein_interactions.ipynb
@@ -54,7 +54,17 @@
"outputs": [],
"source": [
"# prot-prot interactions\n",
- "fp = plf.Fingerprint([\"HBDonor\", \"HBAcceptor\", \"PiStacking\", \"PiCation\", \"CationPi\", \"Anionic\", \"Cationic\"])\n",
+ "fp = plf.Fingerprint(\n",
+ " [\n",
+ " \"HBDonor\",\n",
+ " \"HBAcceptor\",\n",
+ " \"PiStacking\",\n",
+ " \"PiCation\",\n",
+ " \"CationPi\",\n",
+ " \"Anionic\",\n",
+ " \"Cationic\",\n",
+ " ]\n",
+ ")\n",
"fp.run(u.trajectory[::10], tm3, prot)"
]
},
@@ -117,10 +127,7 @@
"outputs": [],
"source": [
"# regroup all interactions together and do the same\n",
- "g = (df.groupby(level=[\"ligand\", \"protein\"], axis=1)\n",
- " .sum()\n",
- " .astype(bool)\n",
- " .mean())\n",
+ "g = df.groupby(level=[\"ligand\", \"protein\"], axis=1).sum().astype(bool).mean()\n",
"g.loc[g > 0.3]"
]
},
@@ -148,12 +155,14 @@
"backbone = Chem.MolFromSmarts(\"[C^2](=O)-[C;X4](-[H])-[N;+0]\")\n",
"fix_h = Chem.MolFromSmarts(\"[H&D0]\")\n",
"\n",
+ "\n",
"def remove_backbone(atomgroup):\n",
" mol = plf.Molecule.from_mda(atomgroup)\n",
" mol = AllChem.DeleteSubstructs(mol, backbone)\n",
" mol = AllChem.DeleteSubstructs(mol, fix_h)\n",
" return plf.Molecule(mol)\n",
"\n",
+ "\n",
"# generate IFP\n",
"ifp = []\n",
"for ts in tqdm(u.trajectory[::10]):\n",
diff --git a/docs/notebooks/quickstart.ipynb b/docs/notebooks/quickstart.ipynb
index bf1be90..a738aa3 100644
--- a/docs/notebooks/quickstart.ipynb
+++ b/docs/notebooks/quickstart.ipynb
@@ -19,6 +19,7 @@
"source": [
"import MDAnalysis as mda\n",
"import prolif as plf\n",
+ "\n",
"# load trajectory\n",
"u = mda.Universe(plf.datafiles.TOP, plf.datafiles.TRAJ)\n",
"# create selections for the ligand and protein\n",
@@ -48,12 +49,13 @@
"source": [
"from rdkit import Chem\n",
"from rdkit.Chem import Draw\n",
+ "\n",
"# create a molecule from the MDAnalysis selection\n",
"lmol = plf.Molecule.from_mda(lig)\n",
"# cleanup before drawing\n",
"mol = Chem.RemoveHs(lmol)\n",
"mol.RemoveAllConformers()\n",
- "Draw.MolToImage(mol, size=(400,200))"
+ "Draw.MolToImage(mol, size=(400, 200))"
]
},
{
@@ -77,11 +79,13 @@
" mol = Chem.RemoveHs(res)\n",
" mol.RemoveAllConformers()\n",
" frags.append(mol)\n",
- "Draw.MolsToGridImage(frags,\n",
- " legends=[str(res.resid) for res in pmol], \n",
- " subImgSize=(200, 140),\n",
- " molsPerRow=4,\n",
- " maxMols=prot.n_residues)"
+ "Draw.MolsToGridImage(\n",
+ " frags,\n",
+ " legends=[str(res.resid) for res in pmol],\n",
+ " subImgSize=(200, 140),\n",
+ " molsPerRow=4,\n",
+ " maxMols=prot.n_residues,\n",
+ ")"
]
},
{
@@ -209,19 +213,36 @@
"\n",
"# reorganize data\n",
"data = df.reset_index()\n",
- "data = pd.melt(data, id_vars=[\"Frame\"], var_name=[\"residue\",\"interaction\"])\n",
+ "data = pd.melt(data, id_vars=[\"Frame\"], var_name=[\"residue\", \"interaction\"])\n",
"data = data[data[\"value\"] != False]\n",
"data.reset_index(inplace=True, drop=True)\n",
"\n",
"# plot\n",
- "sns.set_theme(font_scale=.8, style=\"white\", context=\"talk\")\n",
+ "sns.set_theme(font_scale=0.8, style=\"white\", context=\"talk\")\n",
"g = sns.catplot(\n",
- " data=data, x=\"interaction\", y=\"Frame\", hue=\"interaction\", col=\"residue\",\n",
- " hue_order=[\"Hydrophobic\", \"HBDonor\", \"HBAcceptor\", \"PiStacking\", \"CationPi\", \"Cationic\"],\n",
- " height=3, aspect=0.2, jitter=0, sharex=False, marker=\"_\", s=8, linewidth=3.5,\n",
+ " data=data,\n",
+ " x=\"interaction\",\n",
+ " y=\"Frame\",\n",
+ " hue=\"interaction\",\n",
+ " col=\"residue\",\n",
+ " hue_order=[\n",
+ " \"Hydrophobic\",\n",
+ " \"HBDonor\",\n",
+ " \"HBAcceptor\",\n",
+ " \"PiStacking\",\n",
+ " \"CationPi\",\n",
+ " \"Cationic\",\n",
+ " ],\n",
+ " height=3,\n",
+ " aspect=0.2,\n",
+ " jitter=0,\n",
+ " sharex=False,\n",
+ " marker=\"_\",\n",
+ " s=8,\n",
+ " linewidth=3.5,\n",
")\n",
"g.set_titles(\"{col_name}\")\n",
- "g.set(xticks=[], ylim=(-.5, data.Frame.max()+1))\n",
+ "g.set(xticks=[], ylim=(-0.5, data.Frame.max() + 1))\n",
"g.set_xticklabels([])\n",
"g.set_xlabels(\"\")\n",
"g.fig.subplots_adjust(wspace=0)\n",
@@ -251,10 +272,7 @@
"outputs": [],
"source": [
"# regroup all interactions together and do the same\n",
- "g = (df.groupby(level=[\"protein\"], axis=1)\n",
- " .sum()\n",
- " .astype(bool)\n",
- " .mean())\n",
+ "g = df.groupby(level=[\"protein\"], axis=1).sum().astype(bool).mean()\n",
"g.loc[g > 0.3]"
]
},
@@ -272,6 +290,7 @@
"outputs": [],
"source": [
"from rdkit import DataStructs\n",
+ "\n",
"bvs = fp.to_bitvectors()\n",
"tanimoto_sims = DataStructs.BulkTanimotoSimilarity(bvs[0], bvs)\n",
"tanimoto_sims"
diff --git a/docs/notebooks/visualisation.ipynb b/docs/notebooks/visualisation.ipynb
index 90c0568..c554258 100644
--- a/docs/notebooks/visualisation.ipynb
+++ b/docs/notebooks/visualisation.ipynb
@@ -18,6 +18,7 @@
"import MDAnalysis as mda\n",
"import prolif as plf\n",
"import numpy as np\n",
+ "\n",
"# load topology\n",
"u = mda.Universe(plf.datafiles.TOP, plf.datafiles.TRAJ)\n",
"lig = u.select_atoms(\"resname LIG\")\n",
@@ -42,7 +43,7 @@
"outputs": [],
"source": [
"# get lig-prot interactions with atom info\n",
- "fp = plf.Fingerprint([\"HBDonor\", \"HBAcceptor\", \"Cationic\", \"PiStacking\"])\n",
+ "fp = plf.Fingerprint()\n",
"fp.run(u.trajectory[0:1], lig, prot)\n",
"df = fp.to_dataframe(return_atoms=True)\n",
"df.T"
@@ -68,6 +69,7 @@
"from rdkit import Chem\n",
"from rdkit import Geometry\n",
"\n",
+ "\n",
"def get_ring_centroid(mol, index):\n",
" # find ring using the atom index\n",
" Chem.SanitizeMol(mol, Chem.SanitizeFlags.SANITIZE_SETAROMATICITY)\n",
@@ -76,7 +78,9 @@
" if index in r:\n",
" break\n",
" else:\n",
- " raise ValueError(\"No ring containing this atom index was found in the given molecule\")\n",
+ " raise ValueError(\n",
+ " \"No ring containing this atom index was found in the given molecule\"\n",
+ " )\n",
" # get centroid\n",
" coords = mol.xyz[list(r)]\n",
" ctd = plf.utils.get_centroid(coords)\n",
@@ -101,9 +105,13 @@
"import py3Dmol\n",
"\n",
"colors = {\n",
- " \"HBAcceptor\": \"blue\",\n",
- " \"HBDonor\": \"red\",\n",
- " \"Cationic\": \"green\",\n",
+ " \"Hydrophobic\": \"green\",\n",
+ " \"HBAcceptor\": \"cyan\",\n",
+ " \"HBDonor\": \"cyan\",\n",
+ " \"XBDonor\": \"orange\",\n",
+ " \"XBAcceptor\": \"orange\",\n",
+ " \"Cationic\": \"red\",\n",
+ " \"Anionic\": \"blue\",\n",
" \"PiStacking\": \"purple\",\n",
"}\n",
"\n",
@@ -140,8 +148,10 @@
" lres = lmol[lresid]\n",
" pres = pmol[presid]\n",
" # set model ids for reusing later\n",
- " for resid, res, style in [(lresid, lres, {\"colorscheme\": \"cyanCarbon\"}),\n",
- " (presid, pres, {})]:\n",
+ " for resid, res, style in [\n",
+ " (lresid, lres, {\"colorscheme\": \"cyanCarbon\"}),\n",
+ " (presid, pres, {}),\n",
+ " ]:\n",
" if resid not in models.keys():\n",
" mid += 1\n",
" v.addModel(Chem.MolToMolBlock(res), \"sdf\")\n",
@@ -158,34 +168,35 @@
" if interaction in [\"PiStacking\", \"EdgeToFace\", \"FaceToFace\", \"CationPi\"]:\n",
" p2 = get_ring_centroid(pres, pindex)\n",
" else:\n",
- " p2 = pres.GetConformer().GetAtomPosition(pindex) \n",
+ " p2 = pres.GetConformer().GetAtomPosition(pindex)\n",
" # add interaction line\n",
- " v.addCylinder({\"start\": dict(x=p1.x, y=p1.y, z=p1.z),\n",
- " \"end\": dict(x=p2.x, y=p2.y, z=p2.z),\n",
- " \"color\": colors[interaction],\n",
- " \"radius\": .15,\n",
- " \"dashed\": True,\n",
- " \"fromCap\": 1,\n",
- " \"toCap\": 1,\n",
- " })\n",
+ " v.addCylinder(\n",
+ " {\n",
+ " \"start\": dict(x=p1.x, y=p1.y, z=p1.z),\n",
+ " \"end\": dict(x=p2.x, y=p2.y, z=p2.z),\n",
+ " \"color\": colors.get(interaction, \"grey\"),\n",
+ " \"radius\": 0.15,\n",
+ " \"dashed\": True,\n",
+ " \"fromCap\": 1,\n",
+ " \"toCap\": 1,\n",
+ " }\n",
+ " )\n",
" # add label when hovering the middle of the dashed line by adding a dummy atom\n",
" c = Geometry.Point3D(*plf.utils.get_centroid([p1, p2]))\n",
" modelID = models[lresid]\n",
" model = v.getModel(modelID)\n",
- " model.addAtoms([{\"elem\": 'Z',\n",
- " \"x\": c.x, \"y\": c.y, \"z\": c.z,\n",
- " \"interaction\": interaction}])\n",
- " model.setStyle({\"interaction\": interaction}, {\"clicksphere\": {\"radius\": .5}})\n",
- " model.setHoverable(\n",
- " {\"interaction\": interaction}, True,\n",
- " hover_func, unhover_func)\n",
+ " model.addAtoms(\n",
+ " [{\"elem\": \"Z\", \"x\": c.x, \"y\": c.y, \"z\": c.z, \"interaction\": interaction}]\n",
+ " )\n",
+ " model.setStyle({\"interaction\": interaction}, {\"clicksphere\": {\"radius\": 0.5}})\n",
+ " model.setHoverable({\"interaction\": interaction}, True, hover_func, unhover_func)\n",
"\n",
"# show protein\n",
"mol = Chem.RemoveAllHs(pmol)\n",
"pdb = Chem.MolToPDBBlock(mol, flavor=0x20 | 0x10)\n",
"v.addModel(pdb, \"pdb\")\n",
"model = v.getModel()\n",
- "model.setStyle({}, {\"cartoon\": {\"style\":\"edged\"}})\n",
+ "model.setStyle({}, {\"cartoon\": {\"style\": \"edged\"}})\n",
"\n",
"v.zoomTo({\"model\": list(models.values())})"
]
@@ -218,10 +229,14 @@
"fp.run(u.trajectory[::10], lig, prot)\n",
"df = fp.to_dataframe(return_atoms=True)\n",
"\n",
- "net = LigNetwork.from_ifp(df, lmol,\n",
- " # replace with `kind=\"frame\", frame=0` for the other depiction\n",
- " kind=\"aggregate\", threshold=.3,\n",
- " rotation=270)\n",
+ "net = LigNetwork.from_ifp(\n",
+ " df,\n",
+ " lmol,\n",
+ " # replace with `kind=\"frame\", frame=0` for the other depiction\n",
+ " kind=\"aggregate\",\n",
+ " threshold=0.3,\n",
+ " rotation=270,\n",
+ ")\n",
"net.display()"
]
},
@@ -251,7 +266,6 @@
"source": [
"import networkx as nx\n",
"from pyvis.network import Network\n",
- "from tqdm.auto import tqdm\n",
"from matplotlib import cm, colors\n",
"from IPython.display import IFrame"
]
@@ -278,11 +292,16 @@
"metadata": {},
"outputs": [],
"source": [
- "def make_graph(values, df=None,\n",
- " node_color=[\"#FFB2AC\", \"#ACD0FF\"], node_shape=\"dot\",\n",
- " edge_color=\"#a9a9a9\", width_multiplier=1):\n",
+ "def make_graph(\n",
+ " values,\n",
+ " df=None,\n",
+ " node_color=[\"#FFB2AC\", \"#ACD0FF\"],\n",
+ " node_shape=\"dot\",\n",
+ " edge_color=\"#a9a9a9\",\n",
+ " width_multiplier=1,\n",
+ "):\n",
" \"\"\"Convert a pandas DataFrame to a NetworkX object\n",
- " \n",
+ "\n",
" Parameters\n",
" ----------\n",
" values : pandas.Series\n",
@@ -290,51 +309,65 @@
" each lig-prot residue pair that will be used to set the width and weigth\n",
" of each edge. For example:\n",
"\n",
- " ligand protein \n",
+ " ligand protein\n",
" LIG1.G ALA216.A 0.66\n",
" ALA343.B 0.10\n",
"\n",
" df : pandas.DataFrame\n",
" DataFrame obtained from the fp.to_dataframe() method\n",
" Used to label each edge with the type of interaction\n",
- " \n",
+ "\n",
" node_color : list\n",
" Colors for the ligand and protein residues, respectively\n",
"\n",
" node_shape : str\n",
" One of ellipse, circle, database, box, text or image, circularImage,\n",
" diamond, dot, star, triangle, triangleDown, square, icon.\n",
- " \n",
+ "\n",
" edge_color : str\n",
" Color of the edge between nodes\n",
- " \n",
+ "\n",
" width_multiplier : int or float\n",
" Each edge's width is defined as `width_multiplier * value`\n",
" \"\"\"\n",
" lig_res = values.index.get_level_values(\"ligand\").unique().tolist()\n",
" prot_res = values.index.get_level_values(\"protein\").unique().tolist()\n",
- " \n",
+ "\n",
" G = nx.Graph()\n",
" # add nodes\n",
" # https://pyvis.readthedocs.io/en/latest/documentation.html#pyvis.network.Network.add_node\n",
" for res in lig_res:\n",
- " G.add_node(res, title=res, shape=node_shape,\n",
- " color=node_color[0], dtype=\"ligand\")\n",
+ " G.add_node(\n",
+ " res, title=res, shape=node_shape, color=node_color[0], dtype=\"ligand\"\n",
+ " )\n",
" for res in prot_res:\n",
- " G.add_node(res, title=res, shape=node_shape,\n",
- " color=node_color[1], dtype=\"protein\")\n",
+ " G.add_node(\n",
+ " res, title=res, shape=node_shape, color=node_color[1], dtype=\"protein\"\n",
+ " )\n",
"\n",
" for resids, value in values.items():\n",
- " label = \"{} - {}
{}\".format(*resids, \"
\".join([f\"{k}: {v}\"\n",
- " for k, v in (df.xs(resids,\n",
- " level=[\"ligand\", \"protein\"],\n",
- " axis=1)\n",
- " .sum()\n",
- " .to_dict()\n",
- " .items())]))\n",
+ " label = \"{} - {}
{}\".format(\n",
+ " *resids,\n",
+ " \"
\".join(\n",
+ " [\n",
+ " f\"{k}: {v}\"\n",
+ " for k, v in (\n",
+ " df.xs(resids, level=[\"ligand\", \"protein\"], axis=1)\n",
+ " .sum()\n",
+ " .to_dict()\n",
+ " .items()\n",
+ " )\n",
+ " ]\n",
+ " ),\n",
+ " )\n",
" # https://pyvis.readthedocs.io/en/latest/documentation.html#pyvis.network.Network.add_edge\n",
- " G.add_edge(*resids, title=label, color=edge_color,\n",
- " weight=value, width=value*width_multiplier)\n",
+ " G.add_edge(\n",
+ " *resids,\n",
+ " title=label,\n",
+ " color=edge_color,\n",
+ " weight=value,\n",
+ " width=value * width_multiplier,\n",
+ " )\n",
"\n",
" return G"
]
@@ -354,10 +387,7 @@
"metadata": {},
"outputs": [],
"source": [
- "data = (df.groupby(level=[\"ligand\", \"protein\"], axis=1)\n",
- " .sum()\n",
- " .astype(bool)\n",
- " .mean())\n",
+ "data = df.groupby(level=[\"ligand\", \"protein\"], axis=1).sum().astype(bool).mean()\n",
"\n",
"G = make_graph(data, df, width_multiplier=3)\n",
"\n",
@@ -383,11 +413,10 @@
"metadata": {},
"outputs": [],
"source": [
- "data = (df.xs(\"Hydrophobic\", level=\"interaction\", axis=1)\n",
- " .mean())\n",
+ "data = df.xs(\"Hydrophobic\", level=\"interaction\", axis=1).mean()\n",
"\n",
"G = make_graph(data, df, width_multiplier=3)\n",
- " \n",
+ "\n",
"# display graph\n",
"net = Network(width=600, height=500, notebook=True, heading=\"\")\n",
"net.from_nx(G)\n",
@@ -424,23 +453,25 @@
"metadata": {},
"outputs": [],
"source": [
- "data = (df.groupby(level=[\"ligand\", \"protein\"], axis=1, sort=False)\n",
- " .sum()\n",
- " .astype(bool)\n",
- " .mean())\n",
+ "data = (\n",
+ " df.groupby(level=[\"ligand\", \"protein\"], axis=1, sort=False)\n",
+ " .sum()\n",
+ " .astype(bool)\n",
+ " .mean()\n",
+ ")\n",
"\n",
"G = make_graph(data, df, width_multiplier=8)\n",
"\n",
"# color each node based on its degree\n",
"max_nbr = len(max(G.adj.values(), key=lambda x: len(x)))\n",
- "blues = cm.get_cmap('Blues', max_nbr)\n",
- "reds = cm.get_cmap('Reds', max_nbr)\n",
+ "blues = cm.get_cmap(\"Blues\", max_nbr)\n",
+ "reds = cm.get_cmap(\"Reds\", max_nbr)\n",
"for n, d in G.nodes(data=True):\n",
" n_neighbors = len(G.adj[n])\n",
" # show TM3 in red and the rest of the protein in blue\n",
" palette = reds if d[\"dtype\"] == \"ligand\" else blues\n",
- " d[\"color\"] = colors.to_hex( palette(n_neighbors / max_nbr) )\n",
- " \n",
+ " d[\"color\"] = colors.to_hex(palette(n_neighbors / max_nbr))\n",
+ "\n",
"# convert to pyvis network\n",
"net = Network(width=640, height=500, notebook=True, heading=\"\")\n",
"net.from_nx(G)\n",
@@ -464,7 +495,17 @@
"outputs": [],
"source": [
"prot = u.select_atoms(\"protein\")\n",
- "fp = plf.Fingerprint(['HBDonor', 'HBAcceptor', 'PiStacking', 'Anionic', 'Cationic', 'CationPi', 'PiCation'])\n",
+ "fp = plf.Fingerprint(\n",
+ " [\n",
+ " \"HBDonor\",\n",
+ " \"HBAcceptor\",\n",
+ " \"PiStacking\",\n",
+ " \"Anionic\",\n",
+ " \"Cationic\",\n",
+ " \"CationPi\",\n",
+ " \"PiCation\",\n",
+ " ]\n",
+ ")\n",
"fp.run(u.trajectory[::10], prot, prot)\n",
"df = fp.to_dataframe()\n",
"df.head()"
@@ -483,13 +524,15 @@
"metadata": {},
"outputs": [],
"source": [
- "# remove interactions between residues i and i±4 or less \n",
+ "# remove interactions between residues i and i±4 or less\n",
"mask = []\n",
"for l, p, interaction in df.columns:\n",
" lr = plf.ResidueId.from_string(l)\n",
" pr = plf.ResidueId.from_string(p)\n",
- " if (pr == lr) or (abs(pr.number - lr.number) <= 4\n",
- " and interaction in [\"HBDonor\", \"HBAcceptor\", \"Hydrophobic\"]):\n",
+ " if (pr == lr) or (\n",
+ " abs(pr.number - lr.number) <= 4\n",
+ " and interaction in [\"HBDonor\", \"HBAcceptor\", \"Hydrophobic\"]\n",
+ " ):\n",
" mask.append(False)\n",
" else:\n",
" mask.append(True)\n",
@@ -503,22 +546,24 @@
"metadata": {},
"outputs": [],
"source": [
- "data = (df.groupby(level=[\"ligand\", \"protein\"], axis=1, sort=False)\n",
- " .sum()\n",
- " .astype(bool)\n",
- " .mean())\n",
+ "data = (\n",
+ " df.groupby(level=[\"ligand\", \"protein\"], axis=1, sort=False)\n",
+ " .sum()\n",
+ " .astype(bool)\n",
+ " .mean()\n",
+ ")\n",
"\n",
"G = make_graph(data, df, width_multiplier=5)\n",
"\n",
"# color each node based on its degree\n",
"max_nbr = len(max(G.adj.values(), key=lambda x: len(x)))\n",
- "palette = cm.get_cmap('YlGnBu', max_nbr)\n",
+ "palette = cm.get_cmap(\"YlGnBu\", max_nbr)\n",
"for n, d in G.nodes(data=True):\n",
" n_neighbors = len(G.adj[n])\n",
- " d[\"color\"] = colors.to_hex( palette(n_neighbors / max_nbr) )\n",
- " \n",
+ " d[\"color\"] = colors.to_hex(palette(n_neighbors / max_nbr))\n",
+ "\n",
"# convert to pyvis network\n",
- "net = Network(width=640, height=500, notebook=True, heading=\"\")\n",
+ "net = Network(width=640, height=500, notebook=True, heading=\"\")\n",
"net.from_nx(G)\n",
"\n",
"# use specific layout\n",
@@ -571,7 +616,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.8.5"
+ "version": "3.8.13"
}
},
"nbformat": 4,
diff --git a/docs/source/citation.rst b/docs/source/citation.rst
index c887a46..77838cb 100644
--- a/docs/source/citation.rst
+++ b/docs/source/citation.rst
@@ -8,33 +8,5 @@ If you use ProLIF in your research, please cite the following `paper `_.
diff --git a/prolif/__init__.py b/prolif/__init__.py
index d23658a..e3165b3 100644
--- a/prolif/__init__.py
+++ b/prolif/__init__.py
@@ -1,13 +1,9 @@
-from .molecule import (Molecule,
- pdbqt_supplier,
- mol2_supplier,
- sdf_supplier)
-from .residue import ResidueId
-from .fingerprint import Fingerprint
-from .utils import (get_residues_near_ligand,
- to_dataframe,
- to_bitvectors)
from . import datafiles
from ._version import get_versions
-__version__ = get_versions()['version']
+from .fingerprint import Fingerprint
+from .molecule import Molecule, mol2_supplier, pdbqt_supplier, sdf_supplier
+from .residue import ResidueId
+from .utils import get_residues_near_ligand, to_bitvectors, to_dataframe
+
+__version__ = get_versions()["version"]
del get_versions
diff --git a/prolif/_version.py b/prolif/_version.py
index d1368ea..309a170 100644
--- a/prolif/_version.py
+++ b/prolif/_version.py
@@ -1,4 +1,3 @@
-
# This file helps to compute a version number in source trees obtained from
# git-archive tarball (such as those provided by githubs download-from-tag
# feature). Distribution tarballs (built by setup.py sdist) and build
@@ -58,17 +57,18 @@ class NotThisMethod(Exception):
def register_vcs_handler(vcs, method): # decorator
"""Create decorator to mark a method as the handler of a VCS."""
+
def decorate(f):
"""Store f in HANDLERS[vcs][method]."""
if vcs not in HANDLERS:
HANDLERS[vcs] = {}
HANDLERS[vcs][method] = f
return f
+
return decorate
-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
- env=None):
+def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None):
"""Call the given command(s)."""
assert isinstance(commands, list)
p = None
@@ -76,10 +76,13 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
try:
dispcmd = str([c] + args)
# remember shell=False, so use git.cmd on windows, not just git
- p = subprocess.Popen([c] + args, cwd=cwd, env=env,
- stdout=subprocess.PIPE,
- stderr=(subprocess.PIPE if hide_stderr
- else None))
+ p = subprocess.Popen(
+ [c] + args,
+ cwd=cwd,
+ env=env,
+ stdout=subprocess.PIPE,
+ stderr=(subprocess.PIPE if hide_stderr else None),
+ )
break
except EnvironmentError:
e = sys.exc_info()[1]
@@ -114,16 +117,22 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
for i in range(3):
dirname = os.path.basename(root)
if dirname.startswith(parentdir_prefix):
- return {"version": dirname[len(parentdir_prefix):],
- "full-revisionid": None,
- "dirty": False, "error": None, "date": None}
+ return {
+ "version": dirname[len(parentdir_prefix) :],
+ "full-revisionid": None,
+ "dirty": False,
+ "error": None,
+ "date": None,
+ }
else:
rootdirs.append(root)
root = os.path.dirname(root) # up a level
if verbose:
- print("Tried directories %s but none started with prefix %s" %
- (str(rootdirs), parentdir_prefix))
+ print(
+ "Tried directories %s but none started with prefix %s"
+ % (str(rootdirs), parentdir_prefix)
+ )
raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
@@ -183,7 +192,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
# starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
# just "foo-1.0". If we see a "tag: " prefix, prefer those.
TAG = "tag: "
- tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
+ tags = set([r[len(TAG) :] for r in refs if r.startswith(TAG)])
if not tags:
# Either we're using git < 1.8.3, or there really are no tags. We use
# a heuristic: assume all version tags have a digit. The old git %d
@@ -192,7 +201,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
# between branches and tags. By ignoring refnames without digits, we
# filter out many common branch names like "release" and
# "stabilization", as well as "HEAD" and "master".
- tags = set([r for r in refs if re.search(r'\d', r)])
+ tags = set([r for r in refs if re.search(r"\d", r)])
if verbose:
print("discarding '%s', no digits" % ",".join(refs - tags))
if verbose:
@@ -200,19 +209,26 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
for ref in sorted(tags):
# sorting will prefer e.g. "2.0" over "2.0rc1"
if ref.startswith(tag_prefix):
- r = ref[len(tag_prefix):]
+ r = ref[len(tag_prefix) :]
if verbose:
print("picking %s" % r)
- return {"version": r,
- "full-revisionid": keywords["full"].strip(),
- "dirty": False, "error": None,
- "date": date}
+ return {
+ "version": r,
+ "full-revisionid": keywords["full"].strip(),
+ "dirty": False,
+ "error": None,
+ "date": date,
+ }
# no suitable tags, so version is "0+unknown", but full hex is still there
if verbose:
print("no suitable tags, using unknown + full revision id")
- return {"version": "0+unknown",
- "full-revisionid": keywords["full"].strip(),
- "dirty": False, "error": "no suitable tags", "date": None}
+ return {
+ "version": "0+unknown",
+ "full-revisionid": keywords["full"].strip(),
+ "dirty": False,
+ "error": "no suitable tags",
+ "date": None,
+ }
@register_vcs_handler("git", "pieces_from_vcs")
@@ -227,8 +243,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
if sys.platform == "win32":
GITS = ["git.cmd", "git.exe"]
- out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
- hide_stderr=True)
+ out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True)
if rc != 0:
if verbose:
print("Directory %s not under git control" % root)
@@ -236,10 +251,19 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
# if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
# if there isn't one, this yields HEX[-dirty] (no NUM)
- describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
- "--always", "--long",
- "--match", "%s*" % tag_prefix],
- cwd=root)
+ describe_out, rc = run_command(
+ GITS,
+ [
+ "describe",
+ "--tags",
+ "--dirty",
+ "--always",
+ "--long",
+ "--match",
+ "%s*" % tag_prefix,
+ ],
+ cwd=root,
+ )
# --long was added in git-1.5.5
if describe_out is None:
raise NotThisMethod("'git describe' failed")
@@ -262,17 +286,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
dirty = git_describe.endswith("-dirty")
pieces["dirty"] = dirty
if dirty:
- git_describe = git_describe[:git_describe.rindex("-dirty")]
+ git_describe = git_describe[: git_describe.rindex("-dirty")]
# now we have TAG-NUM-gHEX or HEX
if "-" in git_describe:
# TAG-NUM-gHEX
- mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
+ mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
if not mo:
# unparseable. Maybe git-describe is misbehaving?
- pieces["error"] = ("unable to parse git-describe output: '%s'"
- % describe_out)
+ pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
return pieces
# tag
@@ -281,10 +304,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
if verbose:
fmt = "tag '%s' doesn't start with prefix '%s'"
print(fmt % (full_tag, tag_prefix))
- pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
- % (full_tag, tag_prefix))
+ pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
+ full_tag,
+ tag_prefix,
+ )
return pieces
- pieces["closest-tag"] = full_tag[len(tag_prefix):]
+ pieces["closest-tag"] = full_tag[len(tag_prefix) :]
# distance: number of commits since tag
pieces["distance"] = int(mo.group(2))
@@ -295,13 +320,13 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
else:
# HEX: no tags
pieces["closest-tag"] = None
- count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
- cwd=root)
+ count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root)
pieces["distance"] = int(count_out) # total number of commits
# commit date: see ISO-8601 comment in git_versions_from_keywords()
- date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
- cwd=root)[0].strip()
+ date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[
+ 0
+ ].strip()
# Use only the last line. Previous lines may contain GPG signature
# information.
date = date.splitlines()[-1]
@@ -335,8 +360,7 @@ def render_pep440(pieces):
rendered += ".dirty"
else:
# exception #1
- rendered = "0+untagged.%d.g%s" % (pieces["distance"],
- pieces["short"])
+ rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
return rendered
@@ -450,11 +474,13 @@ def render_git_describe_long(pieces):
def render(pieces, style):
"""Render the given version pieces into the requested style."""
if pieces["error"]:
- return {"version": "unknown",
- "full-revisionid": pieces.get("long"),
- "dirty": None,
- "error": pieces["error"],
- "date": None}
+ return {
+ "version": "unknown",
+ "full-revisionid": pieces.get("long"),
+ "dirty": None,
+ "error": pieces["error"],
+ "date": None,
+ }
if not style or style == "default":
style = "pep440" # the default
@@ -474,9 +500,13 @@ def render(pieces, style):
else:
raise ValueError("unknown style '%s'" % style)
- return {"version": rendered, "full-revisionid": pieces["long"],
- "dirty": pieces["dirty"], "error": None,
- "date": pieces.get("date")}
+ return {
+ "version": rendered,
+ "full-revisionid": pieces["long"],
+ "dirty": pieces["dirty"],
+ "error": None,
+ "date": pieces.get("date"),
+ }
def get_versions():
@@ -490,8 +520,7 @@ def get_versions():
verbose = cfg.verbose
try:
- return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
- verbose)
+ return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose)
except NotThisMethod:
pass
@@ -500,13 +529,16 @@ def get_versions():
# versionfile_source is the relative path from the top of the source
# tree (where the .git directory might live) to this file. Invert
# this to find the root from __file__.
- for i in cfg.versionfile_source.split('/'):
+ for i in cfg.versionfile_source.split("/"):
root = os.path.dirname(root)
except NameError:
- return {"version": "0+unknown", "full-revisionid": None,
- "dirty": None,
- "error": "unable to find root of source tree",
- "date": None}
+ return {
+ "version": "0+unknown",
+ "full-revisionid": None,
+ "dirty": None,
+ "error": "unable to find root of source tree",
+ "date": None,
+ }
try:
pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
@@ -520,6 +552,10 @@ def get_versions():
except NotThisMethod:
pass
- return {"version": "0+unknown", "full-revisionid": None,
- "dirty": None,
- "error": "unable to compute version", "date": None}
+ return {
+ "version": "0+unknown",
+ "full-revisionid": None,
+ "dirty": None,
+ "error": "unable to compute version",
+ "date": None,
+ }
diff --git a/prolif/datafiles.py b/prolif/datafiles.py
index 2afeee1..22c7d60 100644
--- a/prolif/datafiles.py
+++ b/prolif/datafiles.py
@@ -3,4 +3,4 @@
datapath = Path(__file__).parent / "data"
TOP = str(datapath / "top.pdb")
-TRAJ = str(datapath / "traj.xtc")
\ No newline at end of file
+TRAJ = str(datapath / "traj.xtc")
diff --git a/prolif/fingerprint.py b/prolif/fingerprint.py
index d656674..47b76ab 100644
--- a/prolif/fingerprint.py
+++ b/prolif/fingerprint.py
@@ -37,30 +37,17 @@
from .interactions import _INTERACTIONS
from .molecule import Molecule
-from .parallel import (Progress, ProgressCounter,
- declare_shared_objs_for_chunk,
- declare_shared_objs_for_mol, process_chunk, process_mol)
+from .parallel import (
+ Progress,
+ ProgressCounter,
+ declare_shared_objs_for_chunk,
+ declare_shared_objs_for_mol,
+ process_chunk,
+ process_mol,
+)
from .utils import get_residues_near_ligand, to_bitvectors, to_dataframe
-class _Docstring:
- """Descriptor that replaces the documentation shown when calling
- ``fp.hydrophobic?`` and other interaction methods"""
- def __init__(self):
- self._docs = {}
-
- def __set__(self, instance, func):
- # add function's docstring to memory
- cls = func.__self__.__class__
- self._docs[cls.__name__] = cls.__doc__
-
- def __get__(self, instance, owner):
- if instance is None:
- return self
- # fetch docstring of last accessed Fingerprint method
- return self._docs[type(instance)._current_func]
-
-
class _InteractionWrapper:
"""Modifies the return signature of an interaction ``detect`` method by
forcing it to return only the first element when multiple values are
@@ -100,8 +87,6 @@ class _InteractionWrapper:
Changed from a wrapper function to a class for easier pickling support
"""
- __doc__ = _Docstring()
- _current_func = ""
def __init__(self, func):
self.__wrapped__ = func
@@ -199,8 +184,20 @@ class Fingerprint:
"""
- def __init__(self, interactions=["Hydrophobic", "HBDonor", "HBAcceptor",
- "PiStacking", "Anionic", "Cationic", "CationPi", "PiCation"]):
+ def __init__(
+ self,
+ interactions=[
+ "Hydrophobic",
+ "HBDonor",
+ "HBAcceptor",
+ "PiStacking",
+ "Anionic",
+ "Cationic",
+ "CationPi",
+ "PiCation",
+ "VdWContact",
+ ],
+ ):
self._set_interactions(interactions)
def _set_interactions(self, interactions):
@@ -223,13 +220,6 @@ def _set_interactions(self, interactions):
if name in interactions:
self.interactions[name] = func
- def __getattribute__(self, name):
- # trick to get the correct docstring when calling `fp.hydrophobic?`
- attr = super().__getattribute__(name)
- if isinstance(attr, _InteractionWrapper):
- type(attr)._current_func = attr.__wrapped__.__self__.__class__.__name__
- return attr
-
def __repr__(self): # pragma: no cover
name = ".".join([self.__class__.__module__, self.__class__.__name__])
params = f"{self.n_interactions} interactions: {list(self.interactions.keys())}"
@@ -248,9 +238,11 @@ def list_available(show_hidden=False):
if show_hidden:
interactions = [name for name in _INTERACTIONS.keys()]
else:
- interactions = [name for name in _INTERACTIONS.keys()
- if not (name.startswith("_")
- or name == "Interaction")]
+ interactions = [
+ name
+ for name in _INTERACTIONS.keys()
+ if not (name.startswith("_") or name == "Interaction")
+ ]
return sorted(interactions)
@property
@@ -379,7 +371,16 @@ def generate(self, lig, prot, residues=None, return_atoms=False):
ifp[key] = self.bitvector(lres, pres)
return ifp
- def run(self, traj, lig, prot, residues=None, converter_kwargs=None, progress=True, n_jobs=None):
+ def run(
+ self,
+ traj,
+ lig,
+ prot,
+ residues=None,
+ converter_kwargs=None,
+ progress=True,
+ n_jobs=None,
+ ):
"""Generates the fingerprint on a trajectory for a ligand and a protein
Parameters
@@ -441,7 +442,7 @@ def run(self, traj, lig, prot, residues=None, converter_kwargs=None, progress=Tr
.. versionchanged:: 1.0.0
Added support for multiprocessing
-
+
.. versionchanged:: 1.1.0
Added support for passing kwargs to the RDKitConverter through
the ``converter_kwargs`` parameter
@@ -452,9 +453,15 @@ def run(self, traj, lig, prot, residues=None, converter_kwargs=None, progress=Tr
if converter_kwargs is not None and len(converter_kwargs) != 2:
raise ValueError("converter_kwargs must be a list of 2 dicts")
if n_jobs != 1:
- return self._run_parallel(traj, lig, prot, residues=residues,
- converter_kwargs=converter_kwargs,
- progress=progress, n_jobs=n_jobs)
+ return self._run_parallel(
+ traj,
+ lig,
+ prot,
+ residues=residues,
+ converter_kwargs=converter_kwargs,
+ progress=progress,
+ n_jobs=n_jobs,
+ )
lig_kwargs, prot_kwargs = converter_kwargs or ({}, {})
iterator = tqdm(traj) if progress else traj
@@ -464,15 +471,24 @@ def run(self, traj, lig, prot, residues=None, converter_kwargs=None, progress=Tr
for ts in iterator:
prot_mol = Molecule.from_mda(prot, **prot_kwargs)
lig_mol = Molecule.from_mda(lig, **lig_kwargs)
- data = self.generate(lig_mol, prot_mol, residues=residues,
- return_atoms=True)
+ data = self.generate(
+ lig_mol, prot_mol, residues=residues, return_atoms=True
+ )
data["Frame"] = ts.frame
ifp.append(data)
self.ifp = ifp
return self
- def _run_parallel(self, traj, lig, prot, residues=None, converter_kwargs=None,
- progress=True, n_jobs=None):
+ def _run_parallel(
+ self,
+ traj,
+ lig,
+ prot,
+ residues=None,
+ converter_kwargs=None,
+ progress=True,
+ n_jobs=None,
+ ):
"""Parallel implementation of :meth:`~Fingerprint.run`"""
n_chunks = n_jobs if n_jobs else mp.cpu_count()
try:
@@ -497,8 +513,11 @@ def _run_parallel(self, traj, lig, prot, residues=None, converter_kwargs=None,
pbar_thread = Thread(target=pbar, daemon=True)
# run pool of workers
- with mp.Pool(n_jobs, initializer=declare_shared_objs_for_chunk,
- initargs=(self, residues, progress, pcount, (lig_kwargs, prot_kwargs))) as pool:
+ with mp.Pool(
+ n_jobs,
+ initializer=declare_shared_objs_for_chunk,
+ initargs=(self, residues, progress, pcount, (lig_kwargs, prot_kwargs)),
+ ) as pool:
pbar_thread.start()
args = ((traj, lig, prot, chunk) for chunk in chunks)
results = []
@@ -508,8 +527,9 @@ def _run_parallel(self, traj, lig, prot, residues=None, converter_kwargs=None,
self.ifp = results
return self
- def run_from_iterable(self, lig_iterable, prot_mol, residues=None,
- progress=True, n_jobs=None):
+ def run_from_iterable(
+ self, lig_iterable, prot_mol, residues=None, progress=True, n_jobs=None
+ ):
"""Generates the fingerprint between a list of ligands and a protein
Parameters
@@ -571,28 +591,32 @@ def run_from_iterable(self, lig_iterable, prot_mol, residues=None,
raise ValueError("n_jobs must be > 0 or None")
if n_jobs != 1:
return self._run_iter_parallel(
- lig_iterable=lig_iterable, prot_mol=prot_mol,
- residues=residues, progress=progress, n_jobs=n_jobs)
+ lig_iterable=lig_iterable,
+ prot_mol=prot_mol,
+ residues=residues,
+ progress=progress,
+ n_jobs=n_jobs,
+ )
iterator = tqdm(lig_iterable) if progress else lig_iterable
if residues == "all":
residues = prot_mol.residues.keys()
ifp = []
for i, lig_mol in enumerate(iterator):
- data = self.generate(lig_mol, prot_mol, residues=residues,
- return_atoms=True)
+ data = self.generate(
+ lig_mol, prot_mol, residues=residues, return_atoms=True
+ )
data["Frame"] = i
ifp.append(data)
self.ifp = ifp
return self
- def _run_iter_parallel(self, lig_iterable, prot_mol, residues=None,
- progress=True, n_jobs=None):
+ def _run_iter_parallel(
+ self, lig_iterable, prot_mol, residues=None, progress=True, n_jobs=None
+ ):
"""Parallel implementation of :meth:`~Fingerprint.run_from_iterable`"""
- if (
- isinstance(lig_iterable, Chem.SDMolSupplier)
- or (isinstance(lig_iterable, Iterable)
- and not isgenerator(lig_iterable))
+ if isinstance(lig_iterable, Chem.SDMolSupplier) or (
+ isinstance(lig_iterable, Iterable) and not isgenerator(lig_iterable)
):
total = len(lig_iterable)
else:
@@ -601,11 +625,17 @@ def _run_iter_parallel(self, lig_iterable, prot_mol, residues=None,
if residues == "all":
residues = prot_mol.residues.keys()
- with mp.Pool(n_jobs, initializer=declare_shared_objs_for_mol,
- initargs=(self, prot_mol, residues)) as pool:
+ with mp.Pool(
+ n_jobs,
+ initializer=declare_shared_objs_for_mol,
+ initargs=(self, prot_mol, residues),
+ ) as pool:
results = []
- for data in tqdm(pool.imap_unordered(process_mol, suppl),
- total=total, disable=not progress):
+ for data in tqdm(
+ pool.imap_unordered(process_mol, suppl),
+ total=total,
+ disable=not progress,
+ ):
results.append(data)
results.sort(key=lambda ifp: ifp["Frame"])
self.ifp = results
diff --git a/prolif/interactions.py b/prolif/interactions.py
index d6c002f..e2b6d1d 100644
--- a/prolif/interactions.py
+++ b/prolif/interactions.py
@@ -57,11 +57,14 @@ def detect(self, res1, res2, threshold=2.0):
class _InteractionMeta(ABCMeta):
"""Metaclass to register interactions automatically"""
+
def __init__(cls, name, bases, classdict):
type.__init__(cls, name, bases, classdict)
if name in _INTERACTIONS.keys():
- warnings.warn(f"The {name!r} interaction has been superseded by a "
- f"new class with id {id(cls):#x}")
+ warnings.warn(
+ f"The {name!r} interaction has been superseded by a "
+ f"new class with id {id(cls):#x}"
+ )
_INTERACTIONS[name] = cls
@@ -71,6 +74,7 @@ class Interaction(ABC, metaclass=_InteractionMeta):
All interaction classes must inherit this class and define a
:meth:`~detect` method
"""
+
@abstractmethod
def detect(self, **kwargs):
pass
@@ -106,6 +110,7 @@ class _Distance(Interaction):
distance : float
Cutoff distance, measured between the first atom of each pattern
"""
+
def __init__(self, lig_pattern, prot_pattern, distance):
self.lig_pattern = MolFromSmarts(lig_pattern)
self.prot_pattern = MolFromSmarts(prot_pattern)
@@ -115,8 +120,7 @@ def detect(self, lig_res, prot_res):
lig_matches = lig_res.GetSubstructMatches(self.lig_pattern)
prot_matches = prot_res.GetSubstructMatches(self.prot_pattern)
if lig_matches and prot_matches:
- for lig_match, prot_match in product(lig_matches,
- prot_matches):
+ for lig_match, prot_match in product(lig_matches, prot_matches):
alig = Geometry.Point3D(*lig_res.xyz[lig_match[0]])
aprot = Geometry.Point3D(*prot_res.xyz[prot_match[0]])
if alig.Distance(aprot) <= self.distance:
@@ -139,13 +143,13 @@ class Hydrophobic(_Distance):
The initial SMARTS pattern was too broad.
"""
+
def __init__(
self,
hydrophobic=(
- "[c,s,Br,I,S&H0&v2,"
- "$([D3,D4;#6])&!$([#6]~[#7,#8,#9])&!$([#6X4H0]);+0]"
+ "[c,s,Br,I,S&H0&v2," "$([D3,D4;#6])&!$([#6]~[#7,#8,#9])&!$([#6X4H0]);+0]"
),
- distance=4.5
+ distance=4.5,
):
super().__init__(hydrophobic, hydrophobic, distance)
@@ -169,8 +173,9 @@ class _BaseHBond(Interaction):
The initial SMARTS pattern was too broad.
"""
+
def __init__(
- self,
+ self,
donor="[$([O,S;+0]),$([N;v3,v4&+1]),n+0]-[H]",
acceptor=(
"[#7&!$([nX3])&!$([NX3]-*=[O,N,P,S])&!$([NX3]-[a])&!$([Nv4&+1]),"
@@ -178,7 +183,7 @@ def __init__(
"F&$(F-[#6])&!$(F-[#6][F,Cl,Br,I])]"
),
distance=3.5,
- angles=(130, 180)
+ angles=(130, 180),
):
self.donor = MolFromSmarts(donor)
self.acceptor = MolFromSmarts(acceptor)
@@ -189,8 +194,7 @@ def detect(self, acceptor, donor):
acceptor_matches = acceptor.GetSubstructMatches(self.acceptor)
donor_matches = donor.GetSubstructMatches(self.donor)
if acceptor_matches and donor_matches:
- for donor_match, acceptor_match in product(donor_matches,
- acceptor_matches):
+ for donor_match, acceptor_match in product(donor_matches, acceptor_matches):
# D-H ... A
d = Geometry.Point3D(*donor.xyz[donor_match[0]])
h = Geometry.Point3D(*donor.xyz[donor_match[1]])
@@ -207,6 +211,7 @@ def detect(self, acceptor, donor):
class HBDonor(_BaseHBond):
"""Hbond interaction between a ligand (donor) and a residue (acceptor)"""
+
def detect(self, ligand, residue):
bit, ires, ilig = super().detect(residue, ligand)
return bit, ilig, ires
@@ -214,6 +219,7 @@ def detect(self, ligand, residue):
class HBAcceptor(_BaseHBond):
"""Hbond interaction between a ligand (acceptor) and a residue (donor)"""
+
def detect(self, ligand, residue):
return super().detect(ligand, residue)
@@ -238,12 +244,15 @@ class _BaseXBond(Interaction):
-----
Distance and angle adapted from Auffinger et al. PNAS 2004
"""
- def __init__(self,
- donor="[#6,#7,Si,F,Cl,Br,I]-[Cl,Br,I,At]",
- acceptor="[#7,#8,P,S,Se,Te,a;!+{1-}][*]",
- distance=3.5,
- axd_angles=(130, 180),
- xar_angles=(80, 140)):
+
+ def __init__(
+ self,
+ donor="[#6,#7,Si,F,Cl,Br,I]-[Cl,Br,I,At]",
+ acceptor="[#7,#8,P,S,Se,Te,a;!+{1-}][*]",
+ distance=3.5,
+ axd_angles=(130, 180),
+ xar_angles=(80, 140),
+ ):
self.donor = MolFromSmarts(donor)
self.acceptor = MolFromSmarts(acceptor)
self.distance = distance
@@ -254,8 +263,7 @@ def detect(self, acceptor, donor):
acceptor_matches = acceptor.GetSubstructMatches(self.acceptor)
donor_matches = donor.GetSubstructMatches(self.donor)
if acceptor_matches and donor_matches:
- for donor_match, acceptor_match in product(donor_matches,
- acceptor_matches):
+ for donor_match, acceptor_match in product(donor_matches, acceptor_matches):
# D-X ... A distance
d = Geometry.Point3D(*donor.xyz[donor_match[0]])
x = Geometry.Point3D(*donor.xyz[donor_match[1]])
@@ -278,12 +286,14 @@ def detect(self, acceptor, donor):
class XBAcceptor(_BaseXBond):
"""Halogen bonding between a ligand (acceptor) and a residue (donor)"""
+
def detect(self, ligand, residue):
return super().detect(ligand, residue)
class XBDonor(_BaseXBond):
"""Halogen bonding between a ligand (donor) and a residue (acceptor)"""
+
def detect(self, ligand, residue):
bit, ires, ilig = super().detect(residue, ligand)
return bit, ilig, ires
@@ -296,21 +306,26 @@ class _BaseIonic(_Distance):
Handles resonance forms for common acids, amidine and guanidine.
"""
- def __init__(self,
- cation="[+{1-},$([NX3&!$([NX3]-O)]-[C]=[NX3+])]",
- anion="[-{1-},$(O=[C,S,P]-[O-])]",
- distance=4.5):
+
+ def __init__(
+ self,
+ cation="[+{1-},$([NX3&!$([NX3]-O)]-[C]=[NX3+])]",
+ anion="[-{1-},$(O=[C,S,P]-[O-])]",
+ distance=4.5,
+ ):
super().__init__(cation, anion, distance)
class Cationic(_BaseIonic):
"""Ionic interaction between a ligand (cation) and a residue (anion)"""
+
def detect(self, ligand, residue):
return super().detect(ligand, residue)
class Anionic(_BaseIonic):
"""Ionic interaction between a ligand (anion) and a residue (cation)"""
+
def detect(self, ligand, residue):
bit, ires, ilig = super().detect(residue, ligand)
return bit, ilig, ires
@@ -336,11 +351,17 @@ class _BaseCationPi(Interaction):
Handles resonance forms for amidine and guanidine as cations.
"""
- def __init__(self,
- cation="[+{1-},$([NX3&!$([NX3]-O)]-[C]=[NX3+])]",
- pi_ring=("[a;r6]1:[a;r6]:[a;r6]:[a;r6]:[a;r6]:[a;r6]:1", "[a;r5]1:[a;r5]:[a;r5]:[a;r5]:[a;r5]:1"),
- distance=4.5,
- angles=(0, 30)):
+
+ def __init__(
+ self,
+ cation="[+{1-},$([NX3&!$([NX3]-O)]-[C]=[NX3+])]",
+ pi_ring=(
+ "[a;r6]1:[a;r6]:[a;r6]:[a;r6]:[a;r6]:[a;r6]:1",
+ "[a;r5]1:[a;r5]:[a;r5]:[a;r5]:[a;r5]:1",
+ ),
+ distance=4.5,
+ angles=(0, 30),
+ ):
self.cation = MolFromSmarts(cation)
self.pi_ring = [MolFromSmarts(s) for s in pi_ring]
self.distance = distance
@@ -376,6 +397,7 @@ def detect(self, cation, pi):
class PiCation(_BaseCationPi):
"""Cation-Pi interaction between a ligand (aromatic ring) and a residue
(cation)"""
+
def detect(self, ligand, residue):
bit, ires, ilig = super().detect(residue, ligand)
return bit, ilig, ires
@@ -384,13 +406,14 @@ def detect(self, ligand, residue):
class CationPi(_BaseCationPi):
"""Cation-Pi interaction between a ligand (cation) and a residue
(aromatic ring)"""
+
def detect(self, ligand, residue):
return super().detect(ligand, residue)
class _BasePiStacking(Interaction):
"""Base class for Pi-Stacking interactions
-
+
Parameters
----------
centroid_distance : float
@@ -410,15 +433,23 @@ class _BasePiStacking(Interaction):
the ``shortest_distance`` parameter.
"""
- def __init__(self,
- centroid_distance=5.5,
- plane_angle=(0, 35),
- normal_to_centroid_angle=(0, 30),
- pi_ring=("[a;r6]1:[a;r6]:[a;r6]:[a;r6]:[a;r6]:[a;r6]:1", "[a;r5]1:[a;r5]:[a;r5]:[a;r5]:[a;r5]:1")):
+
+ def __init__(
+ self,
+ centroid_distance=5.5,
+ plane_angle=(0, 35),
+ normal_to_centroid_angle=(0, 30),
+ pi_ring=(
+ "[a;r6]1:[a;r6]:[a;r6]:[a;r6]:[a;r6]:[a;r6]:1",
+ "[a;r5]1:[a;r5]:[a;r5]:[a;r5]:[a;r5]:1",
+ ),
+ ):
self.pi_ring = [MolFromSmarts(s) for s in pi_ring]
self.centroid_distance = centroid_distance
- self.plane_angle = tuple(radians(i) for i in plane_angle)
- self.normal_to_centroid_angle = tuple(radians(i) for i in normal_to_centroid_angle)
+ self.plane_angle = tuple(radians(i) for i in plane_angle)
+ self.normal_to_centroid_angle = tuple(
+ radians(i) for i in normal_to_centroid_angle
+ )
self.edge = False
self.ring_radius = 1.7
@@ -437,15 +468,11 @@ def detect(self, ligand, residue):
if cdist > self.centroid_distance:
continue
# ligand
- lig_normal = get_ring_normal_vector(lig_centroid,
- lig_pi_coords)
+ lig_normal = get_ring_normal_vector(lig_centroid, lig_pi_coords)
# residue
- res_normal = get_ring_normal_vector(res_centroid,
- res_pi_coords)
+ res_normal = get_ring_normal_vector(res_centroid, res_pi_coords)
plane_angle = lig_normal.AngleTo(res_normal)
- if not angle_between_limits(
- plane_angle, *self.plane_angle, ring=True
- ):
+ if not angle_between_limits(plane_angle, *self.plane_angle, ring=True):
continue
c1c2 = lig_centroid.DirectionVector(res_centroid)
c2c1 = res_centroid.DirectionVector(lig_centroid)
@@ -454,7 +481,8 @@ def detect(self, ligand, residue):
if not (
angle_between_limits(
n1c1c2, *self.normal_to_centroid_angle, ring=True
- ) or angle_between_limits(
+ )
+ or angle_between_limits(
n2c2c1, *self.normal_to_centroid_angle, ring=True
)
):
@@ -469,7 +497,7 @@ def detect(self, ligand, residue):
# check if intersection point falls ~within plane ring
intersect_dist = min(
lig_centroid.Distance(intersect),
- res_centroid.Distance(intersect)
+ res_centroid.Distance(intersect),
)
if intersect_dist > self.ring_radius:
continue
@@ -478,21 +506,22 @@ def detect(self, ligand, residue):
@staticmethod
def _get_intersect_point(
- plane_normal, plane_centroid, tilted_normal, tilted_centroid,
+ plane_normal,
+ plane_centroid,
+ tilted_normal,
+ tilted_centroid,
):
- # intersect line is orthogonal to both planes normal vectors
+ # intersect line is orthogonal to both planes normal vectors
intersect_direction = plane_normal.CrossProduct(tilted_normal)
# setup system of linear equations to solve
- A = np.array([list(plane_normal), list(tilted_normal), list(intersect_direction)])
+ A = np.array(
+ [list(plane_normal), list(tilted_normal), list(intersect_direction)]
+ )
if np.linalg.det(A) == 0:
return None
- tilted_offset = tilted_normal.DotProduct(
- Geometry.Point3D(*tilted_centroid)
- )
- plane_offset = plane_normal.DotProduct(
- Geometry.Point3D(*plane_centroid)
- )
- d = np.array([[plane_offset], [tilted_offset], [0.]])
+ tilted_offset = tilted_normal.DotProduct(Geometry.Point3D(*tilted_centroid))
+ plane_offset = plane_normal.DotProduct(Geometry.Point3D(*plane_centroid))
+ d = np.array([[plane_offset], [tilted_offset], [0.0]])
# point on intersect line
point = np.linalg.solve(A, d).T[0]
point = Geometry.Point3D(*point)
@@ -505,43 +534,55 @@ def _get_intersect_point(
class FaceToFace(_BasePiStacking):
"""Face-to-face Pi-Stacking interaction between a ligand and a residue"""
- def __init__(self,
- centroid_distance=5.5,
- plane_angle=(0, 35),
- normal_to_centroid_angle=(0, 33),
- pi_ring=("[a;r6]1:[a;r6]:[a;r6]:[a;r6]:[a;r6]:[a;r6]:1", "[a;r5]1:[a;r5]:[a;r5]:[a;r5]:[a;r5]:1")):
+
+ def __init__(
+ self,
+ centroid_distance=5.5,
+ plane_angle=(0, 35),
+ normal_to_centroid_angle=(0, 33),
+ pi_ring=(
+ "[a;r6]1:[a;r6]:[a;r6]:[a;r6]:[a;r6]:[a;r6]:1",
+ "[a;r5]1:[a;r5]:[a;r5]:[a;r5]:[a;r5]:1",
+ ),
+ ):
super().__init__(
centroid_distance=centroid_distance,
plane_angle=plane_angle,
normal_to_centroid_angle=normal_to_centroid_angle,
- pi_ring=pi_ring
+ pi_ring=pi_ring,
)
class EdgeToFace(_BasePiStacking):
"""Edge-to-face Pi-Stacking interaction between a ligand and a residue
-
+
.. versionchanged:: 1.1.0
In addition to the changes made to the base pi-stacking interaction, this
implementation makes sure that the intersection between the perpendicular ring's
plane and the other's plane falls inside the ring.
"""
- def __init__(self,
- centroid_distance=6.5,
- plane_angle=(50, 90),
- normal_to_centroid_angle=(0, 30),
- ring_radius=1.5,
- pi_ring=("[a;r6]1:[a;r6]:[a;r6]:[a;r6]:[a;r6]:[a;r6]:1", "[a;r5]1:[a;r5]:[a;r5]:[a;r5]:[a;r5]:1")):
+
+ def __init__(
+ self,
+ centroid_distance=6.5,
+ plane_angle=(50, 90),
+ normal_to_centroid_angle=(0, 30),
+ ring_radius=1.5,
+ pi_ring=(
+ "[a;r6]1:[a;r6]:[a;r6]:[a;r6]:[a;r6]:[a;r6]:1",
+ "[a;r5]1:[a;r5]:[a;r5]:[a;r5]:[a;r5]:1",
+ ),
+ ):
super().__init__(
centroid_distance=centroid_distance,
plane_angle=plane_angle,
normal_to_centroid_angle=normal_to_centroid_angle,
- pi_ring=pi_ring
+ pi_ring=pi_ring,
)
self.edge = True
self.ring_radius = ring_radius
-
+
def detect(self, ligand, residue):
return super().detect(ligand, residue)
@@ -559,12 +600,13 @@ class PiStacking(Interaction):
.. versionchanged:: 0.3.4
`shortest_distance` has been replaced by `angle_normal_centroid`
-
+
.. versionchanged:: 1.1.0
The implementation now directly calls :class:`EdgeToFace` and :class:`FaceToFace`
instead of overwriting the default parameters with more generic ones.
"""
+
def __init__(self, ftf_kwargs=None, etf_kwargs=None):
self.ftf = FaceToFace(**ftf_kwargs or {})
self.etf = EdgeToFace(**etf_kwargs or {})
@@ -593,21 +635,26 @@ class _BaseMetallic(_Distance):
The initial SMARTS pattern was too broad.
"""
- def __init__(self,
- metal="[Ca,Cd,Co,Cu,Fe,Mg,Mn,Ni,Zn]",
- ligand="[O,#7&!$([nX3])&!$([NX3]-*=[!#6])&!$([NX3]-[a])&!$([NX4]),-{1-};!+{1-}]",
- distance=2.8):
+
+ def __init__(
+ self,
+ metal="[Ca,Cd,Co,Cu,Fe,Mg,Mn,Ni,Zn]",
+ ligand="[O,#7&!$([nX3])&!$([NX3]-*=[!#6])&!$([NX3]-[a])&!$([NX4]),-{1-};!+{1-}]",
+ distance=2.8,
+ ):
super().__init__(metal, ligand, distance)
class MetalDonor(_BaseMetallic):
"""Metallic interaction between a metal and a residue (chelated)"""
+
def detect(self, ligand, residue):
return super().detect(ligand, residue)
class MetalAcceptor(_BaseMetallic):
"""Metallic interaction between a ligand (chelated) and a metal residue"""
+
def detect(self, ligand, residue):
bit, ires, ilig = super().detect(residue, ligand)
return bit, ilig, ires
@@ -627,7 +674,8 @@ class VdWContact(Interaction):
------
ValueError : ``tolerance`` parameter cannot be negative
"""
- def __init__(self, tolerance=.5):
+
+ def __init__(self, tolerance=0.0):
if tolerance >= 0:
self.tolerance = tolerance
else:
@@ -641,12 +689,13 @@ def detect(self, ligand, residue):
lig = la.GetSymbol().upper()
res = ra.GetSymbol().upper()
try:
- vdw = self._vdw_cache[(lig, res)]
+ vdw = self._vdw_cache[frozenset((lig, res))]
except KeyError:
vdw = vdwradii[lig] + vdwradii[res] + self.tolerance
- self._vdw_cache[(lig, res)] = vdw
- dist = (lxyz.GetAtomPosition(la.GetIdx())
- .Distance(rxyz.GetAtomPosition(ra.GetIdx())))
+ self._vdw_cache[frozenset((lig, res))] = vdw
+ dist = lxyz.GetAtomPosition(la.GetIdx()).Distance(
+ rxyz.GetAtomPosition(ra.GetIdx())
+ )
if dist <= vdw:
return True, la.GetIdx(), ra.GetIdx()
return False, None, None
diff --git a/prolif/molecule.py b/prolif/molecule.py
index 9ce8764..c506349 100644
--- a/prolif/molecule.py
+++ b/prolif/molecule.py
@@ -69,6 +69,7 @@ class Molecule(BaseRDKitMol):
See :mod:`prolif.residue` for more information on residues
"""
+
def __init__(self, mol):
super().__init__(mol)
# set mapping of atoms
@@ -115,8 +116,7 @@ def from_mda(cls, obj, selection=None, **kwargs):
"""
ag = obj.select_atoms(selection) if selection else obj.atoms
if ag.n_atoms == 0:
- raise mda.SelectionError(
- f"AtomGroup is empty, please check your selection")
+ raise mda.SelectionError(f"AtomGroup is empty, please check your selection")
mol = ag.convert_to.rdkit(**kwargs)
return cls(mol)
@@ -149,10 +149,12 @@ def from_rdkit(cls, mol, resname="UNL", resnumber=1, chain=""):
return cls(mol)
mol = copy.deepcopy(mol)
for atom in mol.GetAtoms():
- mi = Chem.AtomPDBResidueInfo(f" {atom.GetSymbol():<3.3}",
- residueName=resname,
- residueNumber=resnumber,
- chainId=chain)
+ mi = Chem.AtomPDBResidueInfo(
+ f" {atom.GetSymbol():<3.3}",
+ residueName=resname,
+ residueNumber=resnumber,
+ chainId=chain,
+ )
atom.SetMonomerInfo(mi)
return cls(mol)
@@ -163,7 +165,7 @@ def __iter__(self):
def __getitem__(self, key):
return self.residues[key]
- def __repr__(self): # pragma: no cover
+ def __repr__(self): # pragma: no cover
name = ".".join([self.__class__.__module__, self.__class__.__name__])
params = f"{self.n_residues} residues and {self.GetNumAtoms()} atoms"
return f"<{name} with {params} at {id(self):#x}>"
@@ -211,7 +213,7 @@ class pdbqt_supplier(Sequence):
.. versionchanged:: 1.0.0
Molecule suppliers are now sequences that can be reused, indexed,
and can return their length, instead of single-use generators.
-
+
.. versionchanged:: 1.1.0
Because the PDBQT supplier needs to strip hydrogen atoms before
assigning bond orders from the template, it used to replace them
@@ -222,6 +224,7 @@ class pdbqt_supplier(Sequence):
A lot of irrelevant warnings and logs have been disabled as well.
"""
+
def __init__(self, paths, template, converter_kwargs=None, **kwargs):
self.paths = list(paths)
self.template = template
@@ -242,8 +245,9 @@ def pdbqt_to_mol(self, pdbqt_path):
with catch_warning(message=r"^Failed to guess the mass"):
pdbqt = mda.Universe(pdbqt_path)
# set attributes needed by the converter
- elements = [mda.topology.guessers.guess_atom_element(x)
- for x in pdbqt.atoms.names]
+ elements = [
+ mda.topology.guessers.guess_atom_element(x) for x in pdbqt.atoms.names
+ ]
pdbqt.add_TopologyAttr("elements", elements)
pdbqt.add_TopologyAttr("chainIDs", pdbqt.atoms.segids)
pdbqt.atoms.types = pdbqt.atoms.elements
@@ -330,6 +334,7 @@ class sdf_supplier(Sequence):
and can return their length, instead of single-use generators.
"""
+
def __init__(self, path, **kwargs):
self.path = path
self._suppl = Chem.SDMolSupplier(path, removeHs=False)
@@ -379,6 +384,7 @@ class mol2_supplier(Sequence):
and can return their length, instead of single-use generators.
"""
+
def __init__(self, path, **kwargs):
self.path = path
self._kwargs = kwargs
diff --git a/prolif/parallel.py b/prolif/parallel.py
index 1977c69..c8b86c5 100644
--- a/prolif/parallel.py
+++ b/prolif/parallel.py
@@ -15,8 +15,7 @@ def process_chunk(args):
for ts in traj[chunk]:
lig_mol = Molecule.from_mda(lig, **lig_kwargs)
prot_mol = Molecule.from_mda(prot, **prot_kwargs)
- data = fp.generate(lig_mol, prot_mol, residues=residues,
- return_atoms=True)
+ data = fp.generate(lig_mol, prot_mol, residues=residues, return_atoms=True)
data["Frame"] = ts.frame
ifp.append(data)
if display_progress:
@@ -25,8 +24,9 @@ def process_chunk(args):
return ifp
-def declare_shared_objs_for_chunk(fingerprint, resid_list, show_progressbar,
- progress_counter, rdkitconverter_kwargs):
+def declare_shared_objs_for_chunk(
+ fingerprint, resid_list, show_progressbar, progress_counter, rdkitconverter_kwargs
+):
"""Declares global objects that are available to the pool of workers for
a trajectory"""
global fp, residues, display_progress, pcount, converter_kwargs
@@ -57,6 +57,7 @@ def declare_shared_objs_for_mol(fingerprint, pmol, resid_list):
class ProgressCounter:
"""Tracks the progress of the fingerprint analysis accross the pool of
workers"""
+
def __init__(self):
self.lock = mp.Lock()
self.counter = mp.Value(c_int32)
@@ -65,6 +66,7 @@ def __init__(self):
class Progress:
"""Handles tracking the progress of the ProgressCounter and updating the
tqdm progress bar, from within an independent thread"""
+
def __init__(self, pcount, *args, **kwargs):
self.pbar = tqdm(*args, **kwargs)
self.pcount = pcount
diff --git a/prolif/plotting/network.py b/prolif/plotting/network.py
index 8b77064..3ae002d 100644
--- a/prolif/plotting/network.py
+++ b/prolif/plotting/network.py
@@ -8,25 +8,29 @@
:members:
"""
-from copy import deepcopy
-from collections import defaultdict
-import warnings
import json
import re
+import warnings
+from collections import defaultdict
+from copy import deepcopy
from html import escape
-import pandas as pd
+
import numpy as np
+import pandas as pd
from rdkit import Chem
from rdkit.Chem import rdDepictor
+
from ..residue import ResidueId
from ..utils import requires
+
try:
from IPython.display import HTML
except ModuleNotFoundError:
pass
else:
- warnings.filterwarnings("ignore", # pragma: no cover
- "Consider using IPython.display.IFrame instead")
+ warnings.filterwarnings(
+ "ignore", "Consider using IPython.display.IFrame instead" # pragma: no cover
+ )
class LigNetwork:
@@ -74,6 +78,7 @@ class LigNetwork:
:attr:`LigNetwork.RESIDUE_TYPES` by adding or modifying the
dictionaries inplace.
"""
+
COLORS = {
"interactions": {
"Hydrophobic": "#59e382",
@@ -88,6 +93,7 @@ class LigNetwork:
"PiStacking": "#b559e3",
"EdgeToFace": "#b559e3",
"FaceToFace": "#b559e3",
+ "VdWContact": "#59e3ad",
},
"atoms": {
"C": "black",
@@ -106,41 +112,40 @@ class LigNetwork:
"Acidic": "#e35959",
"Basic": "#5979e3",
"Polar": "#59bee3",
- "Sulfur": "#e3ce59"
- }
+ "Sulfur": "#e3ce59",
+ },
}
RESIDUE_TYPES = {
- 'ALA': "Aliphatic",
- 'GLY': "Aliphatic",
- 'ILE': "Aliphatic",
- 'LEU': "Aliphatic",
- 'PRO': "Aliphatic",
- 'VAL': "Aliphatic",
- 'PHE': "Aromatic",
- 'TRP': "Aromatic",
- 'TYR': "Aromatic",
- 'ASP': "Acidic",
- 'GLU': "Acidic",
- 'ARG': "Basic",
- 'HIS': "Basic",
- 'HID': "Basic",
- 'HIE': "Basic",
- 'HIP': "Basic",
- 'HSD': "Basic",
- 'HSE': "Basic",
- 'HSP': "Basic",
- 'LYS': "Basic",
- 'SER': "Polar",
- 'THR': "Polar",
- 'ASN': "Polar",
- 'GLN': "Polar",
- 'CYS': "Sulfur",
- 'CYM': "Sulfur",
- 'CYX': "Sulfur",
- 'MET': "Sulfur",
+ "ALA": "Aliphatic",
+ "GLY": "Aliphatic",
+ "ILE": "Aliphatic",
+ "LEU": "Aliphatic",
+ "PRO": "Aliphatic",
+ "VAL": "Aliphatic",
+ "PHE": "Aromatic",
+ "TRP": "Aromatic",
+ "TYR": "Aromatic",
+ "ASP": "Acidic",
+ "GLU": "Acidic",
+ "ARG": "Basic",
+ "HIS": "Basic",
+ "HID": "Basic",
+ "HIE": "Basic",
+ "HIP": "Basic",
+ "HSD": "Basic",
+ "HSE": "Basic",
+ "HSP": "Basic",
+ "LYS": "Basic",
+ "SER": "Polar",
+ "THR": "Polar",
+ "ASN": "Polar",
+ "GLN": "Polar",
+ "CYS": "Sulfur",
+ "CYM": "Sulfur",
+ "CYX": "Sulfur",
+ "MET": "Sulfur",
}
- _LIG_PI_INTERACTIONS = ["EdgeToFace", "FaceToFace", "PiStacking",
- "PiCation"]
+ _LIG_PI_INTERACTIONS = ["EdgeToFace", "FaceToFace", "PiStacking", "PiCation"]
_JS_TEMPLATE = """
var ifp, legend, nodes, edges, legend_buttons;
function drawGraph(_id, nodes, edges, options) {
@@ -189,8 +194,16 @@ class LigNetwork: