Skip to content

Commit 48c158f

Browse files
Merge pull request #27 from alexandrefassio/master
Latest version
2 parents 0a987f7 + 1c25edc commit 48c158f

File tree

10 files changed

+3089
-5
lines changed

10 files changed

+3089
-5
lines changed

luna/MyBio/PDB/Residue.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,24 @@ def get_atom(self):
218218
def set_as_target(self, is_target=True):
219219
self._is_target = is_target
220220

221+
def as_json(self):
222+
223+
if self.is_water():
224+
comp_repr = "sphere"
225+
elif self.is_hetatm():
226+
if len(self.child_list) == 1 or len([atm for atm in self.child_list if atm.element != "H"]) == 1:
227+
comp_repr = "sphere"
228+
else:
229+
comp_repr = "stick"
230+
else:
231+
comp_repr = "stick"
232+
233+
return {"chain": self.parent.id,
234+
"name": self.resname,
235+
"number": self.id[1],
236+
"icode": self.id[2].strip(),
237+
"repr": comp_repr}
238+
221239

222240
class DisorderedResidue(DisorderedEntityWrapper):
223241
"""DisorderedResidue is a wrapper around two or more Residue objects.

luna/mol/atom.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ def get_neighbor_info(self, atom):
128128
def is_neighbor(self, atom):
129129
return atom.serial_number in [i.serial_number for i in self._nb_info]
130130

131+
def as_json(self):
132+
full_id = self.get_full_id()
133+
134+
return {"pdb_id": full_id[0],
135+
"model": full_id[1],
136+
"chain": full_id[2],
137+
"res_name": self.parent.resname,
138+
"res_id": [full_id[3][1], full_id[2].strip()],
139+
"name": [full_id[4][0], full_id[4][1].strip()]}
140+
131141
def __getattr__(self, attr):
132142
if hasattr(self._atom, attr):
133143
return getattr(self._atom, attr)

luna/mol/entry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ def from_string(cls, entry_str, sep=ENTRY_SEPARATOR):
199199

200200
@property
201201
def full_id(self):
202-
return (self.pdb_id, self.mol_id)
202+
return (self.pdb_id, self.chain_id)
203203

204204

205205
class CompoundEntry(Entry):

luna/mol/groups.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,18 @@ def has_target(self):
407407
"""Return 1 if at least one compound is the target."""
408408
return any([a.parent.is_target() for a in self.atoms])
409409

410+
def as_json(self):
411+
grp_obj = {}
412+
413+
grp_obj["atoms"] = [atm.as_json() for atm in self.atoms]
414+
415+
grp_obj["compounds"] = [comp.as_json() for comp in self.compounds]
416+
417+
grp_obj["features"] = [feat.name for feat in self.features if feat.name != "Atom"]
418+
grp_obj["classes"] = [comps.get_class() for comps in self.compounds]
419+
420+
return grp_obj
421+
410422
def clear_refs(self):
411423
if self._recursive:
412424
for atm in self.atoms:

luna/mol/interaction/calc.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from operator import le, ge
33
from itertools import combinations, product
44
from collections import defaultdict
5+
import json
56

67
import luna.mol.interaction.math as im
78
from luna.mol.interaction.conf import DefaultInteractionConf, InteractionConf
@@ -100,6 +101,11 @@ def to_csv(self, output_file):
100101
# Sort lines before writing to always keep the same order.
101102
OUT.write("\n".join([",".join(k) for k in sorted(interactions_set)]))
102103

104+
def to_json(self, output_file=None, indent=None):
105+
with open(output_file, 'w') as OUT:
106+
inter_objs = [inter.as_json() for inter in self.interactions]
107+
json.dump(inter_objs, OUT, indent=indent)
108+
103109
def save(self, output_file, compressed=True):
104110
pickle_data(self, output_file, compressed)
105111

luna/mol/interaction/type.py

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
import numpy as np
22

33
from luna.mol.interaction.math import atom_coordinates, centroid
4+
from luna.util.default_values import PYMOL_INTERACTION_COLOR_AS_RGB
5+
from luna.util import rgb2hex
6+
7+
8+
NUCLEOPHILE_INTERS = ["Orthogonal multipolar", "Parallel multipolar", "Antiparallel multipolar", "Tilted multipolar", "Multipolar",
9+
"Cation-nucleophile", "Unfavorable anion-nucleophile", "Unfavorable nucleophile-nucleophile"]
10+
11+
ELECTROPHILE_INTERS = ["Orthogonal multipolar", "Parallel multipolar", "Antiparallel multipolar", "Tilted multipolar", "Multipolar",
12+
"Anion-electrophile", "Unfavorable cation-electrophile", "Unfavorable electrophile-electrophile"]
13+
14+
UNFAVORABLE_INTERS = ["Repulsive", "Unfavorable anion-nucleophile", "Unfavorable cation-electrophile",
15+
"Unfavorable nucleophile-nucleophile", "Unfavorable electrophile-electrophile"]
416

517

618
class InteractionType():
@@ -66,7 +78,21 @@ def src_centroid(self):
6678
if self._src_interacting_atms:
6779
self._src_centroid = centroid(atom_coordinates(self._src_interacting_atms))
6880
else:
69-
self._src_centroid = self._src_grp.centroid
81+
src_centroid = self._src_grp.centroid
82+
# Define the centroid in a nucleophile with two atoms as the position of its more electronegative atom.
83+
# Remember that the position in the interaction object matters. We have defined that the first group is always
84+
# the nucleophile for both dipole-dipole and ion-dipole interactions.
85+
if self.type in NUCLEOPHILE_INTERS and len(self.src_grp.atoms) == 2:
86+
dipole_atm = self.src_grp.atoms[0] if (self.src_grp.atoms[0].electronegativity
87+
> self.src_grp.atoms[1].electronegativity) else self.src_grp.atoms[1]
88+
src_centroid = dipole_atm.coord
89+
# For unfavorable multipolar interactions, it may happen that the first atom group is an electrophile as well.
90+
elif self.type == "Unfavorable electrophile-electrophile" and len(self.src_grp.atoms) == 2:
91+
dipole_atm = self.src_grp.atoms[0] if (self.src_grp.atoms[0].electronegativity
92+
< self.src_grp.atoms[1].electronegativity) else self.src_grp.atoms[1]
93+
src_centroid = dipole_atm.coord
94+
95+
self._src_centroid = src_centroid
7096
return self._src_centroid
7197

7298
@src_centroid.setter
@@ -82,7 +108,22 @@ def trgt_centroid(self):
82108
if self._trgt_interacting_atms:
83109
self._trgt_centroid = centroid(atom_coordinates(self._trgt_interacting_atms))
84110
else:
85-
self._trgt_centroid = self._trgt_grp.centroid
111+
trgt_centroid = self._trgt_grp.centroid
112+
113+
# Define the centroid in an electrophile with two atoms as the position of its less electronegative atom.
114+
# Remember that the position in the interaction object matters. We have defined that the second group is always
115+
# the electrophile for both dipole-dipole and ion-dipole interactions.
116+
if self.type in ELECTROPHILE_INTERS and len(self.trgt_grp.atoms) == 2:
117+
dipole_atm = self.trgt_grp.atoms[0] if (self.trgt_grp.atoms[0].electronegativity
118+
< self.trgt_grp.atoms[1].electronegativity) else self.trgt_grp.atoms[1]
119+
trgt_centroid = dipole_atm.coord
120+
# For unfavorable multipolar interactions, it may happen that the second atom group is a nucleophile as well.
121+
elif self.type == "Unfavorable nucleophile-nucleophile" and len(self.trgt_grp.atoms) == 2:
122+
dipole_atm = self.trgt_grp.atoms[0] if (self.trgt_grp.atoms[0].electronegativity
123+
> self.trgt_grp.atoms[1].electronegativity) else self.trgt_grp.atoms[1]
124+
trgt_centroid = dipole_atm.coord
125+
126+
self._trgt_centroid = trgt_centroid
86127
return self._trgt_centroid
87128

88129
@trgt_centroid.setter
@@ -133,6 +174,34 @@ def is_intramol_interaction(self):
133174
def is_intermol_interaction(self):
134175
return not self.is_intramol_interaction()
135176

177+
def show_src_centroid(self):
178+
show_centroid = True
179+
180+
# Define the centroid in a nucleophile with two atoms as the position of its more electronegative atom.
181+
# Remember that the position in the interaction object matters. We have defined that the first group is always
182+
# the nucleophile for both dipole-dipole and ion-dipole interactions.
183+
if self.type in NUCLEOPHILE_INTERS and len(self.src_grp.atoms) == 2:
184+
show_centroid = False
185+
# For unfavorable multipolar interactions, it may happen that the first atom group is an electrophile as well.
186+
elif self.type == "Unfavorable electrophile-electrophile" and len(self.src_grp.atoms) == 2:
187+
show_centroid = False
188+
189+
return show_centroid
190+
191+
def show_trgt_centroid(self):
192+
show_centroid = True
193+
194+
# Define the centroid in an electrophile with two atoms as the position of its less electronegative atom.
195+
# Remember that the position in the interaction object matters. We have defined that the second group is always
196+
# the electrophile for both dipole-dipole and ion-dipole interactions.
197+
if self.type in ELECTROPHILE_INTERS and len(self.trgt_grp.atoms) == 2:
198+
show_centroid = False
199+
# For unfavorable multipolar interactions, it may happen that the second atom group is a nucleophile as well.
200+
elif self.type == "Unfavorable nucleophile-nucleophile" and len(self.trgt_grp.atoms) == 2:
201+
show_centroid = False
202+
203+
return show_centroid
204+
136205
def apply_refs(self):
137206
self.src_grp.add_interactions([self])
138207
self.trgt_grp.add_interactions([self])
@@ -145,11 +214,45 @@ def _expand_dict(self):
145214
for key in self._params:
146215
self.__dict__[key] = self._params[key]
147216

217+
def as_json(self):
218+
inter_obj = {}
219+
inter_obj["type"] = self.type
220+
221+
inter_obj["color"] = rgb2hex(*PYMOL_INTERACTION_COLOR_AS_RGB.get_unnormalized_color(self.type))
222+
inter_obj["is_directional"] = self.is_directional()
223+
inter_obj["is_intramol_interaction"] = self.is_intramol_interaction()
224+
inter_obj["is_intermol_interaction"] = self.is_intermol_interaction()
225+
226+
src_grp_obj = self.src_grp.as_json()
227+
src_grp_obj["add_pseudo_group"] = len(self.src_grp.atoms) > 1
228+
src_grp_obj["centroid"] = self.src_centroid.tolist()
229+
src_grp_obj["show_centroid"] = self.show_src_centroid()
230+
231+
if src_grp_obj["add_pseudo_group"]:
232+
src_grp_obj["pseudo_group_name"] = "+".join([a.name for a in sorted(self.src_grp.atoms)])
233+
234+
inter_obj["src_grp"] = src_grp_obj
235+
236+
#
237+
# Target atom group
238+
#
239+
trgt_grp_obj = self.trgt_grp.as_json()
240+
trgt_grp_obj["add_pseudo_group"] = len(self.trgt_grp.atoms) > 1
241+
trgt_grp_obj["centroid"] = self.trgt_centroid.tolist()
242+
trgt_grp_obj["show_centroid"] = self.show_trgt_centroid()
243+
244+
if trgt_grp_obj["add_pseudo_group"]:
245+
trgt_grp_obj["pseudo_group_name"] = "+".join([a.name for a in sorted(self.trgt_grp.atoms)])
246+
247+
inter_obj["trgt_grp"] = trgt_grp_obj
248+
249+
return inter_obj
250+
148251
def __eq__(self, other):
149252
"""Overrides the default implementation"""
150253
if isinstance(self, other.__class__):
151-
is_equal_compounds = ((self.src_grp == other.src_grp and self.trgt_grp == other.trgt_grp) or
152-
(self.src_grp == other.trgt_grp and self.trgt_grp == other.src_grp))
254+
is_equal_compounds = ((self.src_grp == other.src_grp and self.trgt_grp == other.trgt_grp)
255+
or (self.src_grp == other.trgt_grp and self.trgt_grp == other.src_grp))
153256

154257
is_equal_interactions = self.type == other.type
155258
has_equal_params = self.params == other.params

luna/run.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ def main():
8484
parser.add_argument('--out_sim', dest="out_sim", action="store_true",
8585
help="defines whether it should generate a similarity matrix")
8686

87+
parser.add_argument("--nproc", dest="nproc", type=int,
88+
help="the number of processors to use")
89+
8790
args = parser.parse_args()
8891

8992
proj_pkl_file = "%s/project_v%s.pkl.gz" % (args.working_path, version)
@@ -108,6 +111,9 @@ def main():
108111
opt["calc_ifp"] = False
109112
opt["out_pse"] = args.out_pse
110113

114+
if args.nproc:
115+
opt["nproc"] = args.nproc
116+
111117
if args.binding_modes_file:
112118
opt["binding_mode_filter"] = BindingModeFilter.from_config_file(args.binding_modes_file)
113119

luna/util/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
logger = logging.getLogger()
44

55

6+
def rgb2hex(r, g, b):
7+
return "#{:02x}{:02x}{:02x}".format(r, g, b)
8+
9+
10+
def hex2rgb(hexcode):
11+
return tuple(map(ord, hexcode[1:].decode('hex')))
12+
13+
614
# TODO: Transform hex to rgb and vice versa
715
class ColorPallete:
816
def __init__(self, color_map=None, default_color=(255, 255, 255)):
@@ -21,6 +29,15 @@ def get_normalized_color(self, key):
2129
return color
2230
return tuple(c / 255 for c in color)
2331

32+
def get_unnormalized_color(self, key):
33+
color = self.get_color(key)
34+
35+
if isinstance(color, str):
36+
return color
37+
elif all([x > 1 for x in color]):
38+
return color
39+
return tuple(int(c * 255) for c in color)
40+
2441
def get_color(self, key):
2542
if key in self.color_map:
2643
return self.color_map[key]

luna/util/default_values.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,50 @@
213213
"Aromatic bond": "black",
214214
"Other bond": "black",
215215
}, "white")
216+
217+
PYMOL_INTERACTION_COLOR_AS_RGB = ColorPallete({
218+
"Proximal": (0.6, 0.6, 0.6),
219+
"Hydrogen bond": (0.3, 0.3, 1.0),
220+
"Water-bridged hydrogen bond": (0.75, 0.75, 1.0),
221+
"Weak hydrogen bond": (0.4, 0.7, 0.7),
222+
"Ionic": (0.0, 1.0, 0.0),
223+
"Salt bridge": (0.2, 0.6, 0.2),
224+
"Cation-pi": (1.0, 0.6, 0.6),
225+
"Amide-aromatic stacking": (0.70, 0.30, 0.40),
226+
"Hydrophobic": (1.0, 0.5, 0.0),
227+
"Halogen bond": (0.5, 1, 1),
228+
"Halogen-pi": (0.5, 1, 1),
229+
"Chalcogen bond": (1.0, 0.8, 0.5),
230+
"Chalcogen-pi": (1.0, 0.8, 0.5),
231+
"Repulsive": (0.55, 0.25, 0.60),
232+
"Covalent bond": (0.0, 0.0, 0.0),
233+
"Atom overlap": (0.4, 0.4, 0.4),
234+
"Van der Waals clash": (0.9, 0.9, 0.9),
235+
"Van der Waals": (0.5, 0.5, 0.5),
236+
"Orthogonal multipolar": (1.0, 1.0, 0.5),
237+
"Parallel multipolar": (1.0, 1.0, 0.5),
238+
"Antiparallel multipolar": (1.0, 1.0, 0.5),
239+
"Tilted multipolar": (1.0, 1.0, 0.5),
240+
"Multipolar": (1.0, 1.0, 0.5),
241+
"Unfavorable nucleophile-nucleophile": (0.70, 0.50, 0.50),
242+
"Unfavorable electrophile-electrophile": (0.70, 0.50, 0.50),
243+
"Cation-nucleophile": (0.65, 0.9, 0.65),
244+
"Anion-electrophile": (0.65, 0.9, 0.65),
245+
"Unfavorable anion-nucleophile": (0.70, 0.50, 0.50),
246+
"Unfavorable cation-electrophile": (0.70, 0.50, 0.50),
247+
"Pi-stacking": (1.0, 0.2, 0.2),
248+
"Face-to-face pi-stacking": (1.0, 0.2, 0.2),
249+
"Face-to-edge pi-stacking": (1.0, 0.2, 0.2),
250+
"Face-to-slope pi-stacking": (1.0, 0.2, 0.2),
251+
"Edge-to-edge pi-stacking": (1.0, 0.2, 0.2),
252+
"Edge-to-face pi-stacking": (1.0, 0.2, 0.2),
253+
"Edge-to-slope pi-stacking": (1.0, 0.2, 0.2),
254+
"Displaced face-to-face pi-stacking": (1.0, 0.2, 0.2),
255+
"Displaced face-to-edge pi-stacking": (1.0, 0.2, 0.2),
256+
"Displaced face-to-slope pi-stacking": (1.0, 0.2, 0.2),
257+
"Single bond": (0.0, 0.0, 0.0),
258+
"Double bond": (0.0, 0.0, 0.0),
259+
"Triple bond": (0.0, 0.0, 0.0),
260+
"Aromatic bond": (0.0, 0.0, 0.0),
261+
"Other bond": (0.0, 0.0, 0.0),
262+
}, (1.0, 1.0, 1.0))

0 commit comments

Comments
 (0)