IMP logo
IMP Reference Guide  develop.63b38c487d,2024/12/21
The Integrative Modeling Platform
buildxlinks.py
1 """@namespace IMP.EMageFit.buildxlinks
2  Utility functions to handle cross links.
3 """
4 
5 import IMP
6 import IMP.atom as atom
7 import IMP.algebra as alg
8 import IMP.core as core
9 import logging
10 import networkx as nx
11 
12 
13 log = logging.getLogger("buildxlinks")
14 
15 
16 class Xlink:
17 
18  """
19  Class defining a cross-link
20  """
21 
22  def __init__(self, id1, chain1, residue1,
23  id2, chain2, residue2,
24  distance):
25  """
26  Initialize the class
27  @param[in] id1 Id of the first component of the cross-link
28  @param[in] chain1 Chain of the first component
29  @param[in] residue1 Residue cross-linked in the first component
30  @param[in] id2 Id of the second component of the cross-link
31  @param[in] chain2 chain1 Chain of the second component
32  @param[in] residue2 Residue cross-linked in the second component
33  @param[in] distance Maximum distance
34  """
35  self.first_id = id1
36  self.first_chain = chain1
37  self.first_residue = residue1
38  self.second_id = id2
39  self.second_chain = chain2
40  self.second_residue = residue2
41  self.distance = distance
42 
43  def clone(self):
44  xl = Xlink(self.first_id, self.first_chain, self.first_residue,
45  self.second_id, self.second_chain, self.second_residue,
46  self.distance)
47  return xl
48 
49  def __eq__(self, other):
50  if self.first_id != other.first_id or \
51  self.second_id != other.second_id or \
52  self.first_chain != other.first_chain or \
53  self.second_chain != other.second_chain or \
54  self.first_residue != other.first_residue or \
55  self.second_residue != other.second_residue or \
56  abs(other.distance - self.distance) > 0.01:
57  return False
58  return True
59 
60  def swap(self):
61  """
62  swaps the order of the residues in the restraint
63  """
64  self.first_id, self.second_id = self.second_id, self.first_id
65  self.first_residue, self.second_residue = \
66  self.second_residue, self.first_residue
67  self.first_chain, self.second_chain = \
68  self.second_chain, self.first_chain
69 
70  def show(self):
71  s = "Cross Link: %s %s %d - %s %s %d. Distance %f" % (
72  self.first_id, self.first_chain, self.first_residue,
73  self.second_id, self.second_chain, self.second_residue,
74  self.distance)
75  return s
76 
77  def get_name(self):
78  """
79  Generate a unique name for the restraint.
80  @note: The name cannot start with a number, upsets sqlite
81  """
82  name = "cl_%s_%s%d_%s_%s%d" % (self.first_id, self.first_chain,
83  self.first_residue, self.second_id,
84  self.second_chain, self.second_residue)
85  return name
86 
87 
88 def build_xlinks_graph(xlinks_dict):
89  """
90  Build a set of nodes and edges from the set of crosslinking
91  restraints.
92  @param xlinks_dict a XlinksDict class
93 
94  """
95  subunits = set()
96  edges = []
97  for key in xlinks_dict.keys():
98  subunits.add(key[0])
99  subunits.add(key[1])
100  edge = sorted([key[0], key[1]])
101  if edge not in edges:
102  edges.append(edge)
103  log.debug("Subunits %s", subunits)
104  log.debug("Edges %s", edges)
105  return subunits, edges
106 
107 
108 class XlinksDict(dict):
109 
110  """
111  Description of crosslinking restraints as a python
112  dictionary.
113  The keys are a pair with the ids of the cross-linked subunits.
114  Note: The pairs are considered in alphabetic order
115  """
116 
117  def add(self, xlink):
118  """
119  Add a xlink. It is ensured that the id of the first element is
120  is lower that the second
121  """
122  if xlink.second_id < xlink.first_id:
123  xlink.swap()
124  key = (xlink.first_id, xlink.second_id)
125  if key not in self.keys():
126  self[key] = []
127  self[key].append(xlink)
128 
129  def get_xlinks_for_pair(self, pair_ids):
130  """
131  @param pair_ids Ids for the subunits that are cross-linked
132  """
133  try:
134  xlinks_list = self[pair_ids]
135  ys = [xl.clone() for xl in xlinks_list]
136 
137  except KeyError:
138  try:
139  xlinks_list = self[(pair_ids[1], pair_ids[0])]
140  ys = [xl.clone() for xl in xlinks_list]
141  # Ensure that the first element of the pair corresponds to the
142  # first id in the xlink
143  for y in ys:
144  y.swap()
145  except KeyError as e:
146  raise e
147  return ys
148 
149  """
150  try:
151  return self[pair_ids]
152  except KeyError:
153  print pair_ids,"NOT FOUND, swapping to",(pair_ids[1], pair_ids[0])
154  # If not found, invert the pair
155  xlinks_list = self[(pair_ids[1], pair_ids[0])]
156  # swap the xlinks, so the first element of the pair corresponds
157  # to the first id in the xlink
158  for xl in xlinks_list:
159  xl.swap()
160  for xl in xlinks_list:
161  print xl.show()
162  return xlinks_list
163  """
164 
165 
166 class DockOrder:
167 
168  """
169  Compute the order of the docking experiments. The order is derived
170  from the cross-linking restraints:
171  1) The subunit with the highest number of cross-links with others
172  works as the first receptor (A)
173  2) All the subunits cross-linked with A are docked into A.
174  3) The next receptor (B) is the subunit that has the highest number of
175  cross-links after A.
176  4) All the subunits cross-linked to B are docked into B (except A)
177  5) The procedure is repeated until there are no more cross-links
178  """
179 
180  def __init__(self,):
181  self.G = nx.Graph()
182 
183  def set_components_and_connections(self, subunits, edges):
184  """
185  Instead of setting the xlink restraints, init the graph directly
186  Example of what to pass with the structure in 3sfd
187  G.add_nodes_from(["A", "B", "C", "D"])
188  G.add_edges_from([("B","C"), ("B","A"), ("B","D"), ("C","D")])
189  ("B","C") means that there are cross-links between B and C
190  """
191  self.G.add_nodes_from(subunits)
192  self.G.add_edges_from(edges)
193 
194  def set_xlinks(self, xlinks_dict):
195  """
196  Sets the xlinks used for computing the docking order
197  @param xlinks_dict XlinksDict class
198  """
199  subunits, edges = build_xlinks_graph(xlinks_dict)
200  self.set_components_and_connections(subunits, edges)
201 
202  def get_docking_order(self):
203  """ return the order to dock components from the cross links """
204  docking_pairs = []
205  degs = self.G.degree(self.G.nodes())
206  log.debug("Degrees: %s", degs)
207  sorted_degrees = sorted(((v, k) for (k, v) in dict(degs).items()),
208  reverse=True)
209 
210  receptors_considered = []
211  for degree, node in sorted_degrees:
212  for n in self.G.neighbors(node):
213  if n not in receptors_considered:
214  docking_pairs.append((node, n))
215  receptors_considered.append(node)
216  log.info("The suggested order for the docking pairs is %s",
217  docking_pairs)
218  return docking_pairs
219 
220 
222 
223  """
224  Puts two subunits together using the Xlinkins restraints. The solutions
225  offered by this class are just an initial position of the components
226  to be fed to HEX
227  """
228 
229  def clear_xlinks(self):
230  self.xlinks_list = []
231 
232  def set_xlinks(self, xlinks_list):
233  """
234  Sets the xlinks used for the computation for the initial rough
235  docking solution
236  @param xlinks_list A list of Xlink classes
237  residue1 belongs to the receptor and residue2 belongs to the ligand
238  """
239  self.xlinks_list = xlinks_list
240 
241  def set_pdbs(self, fn_receptor, fn_ligand):
242  """
243  Set the name of the PDB files of the receptor and the ligand
244  @param fn_receptor
245  @param fn_ligand
246  """
247  sel = atom.ATOMPDBSelector()
248  self.m_receptor = IMP.Model()
249  self.h_receptor = atom.read_pdb(fn_receptor, self.m_receptor, sel)
250  self.m_ligand = IMP.Model()
251  self.h_ligand = atom.read_pdb(fn_ligand, self.m_ligand, sel)
252 
253  def set_hierarchies(self, h_receptor, h_ligand):
254  """
255  Set the hierarchies (atom.Hierarchy objects) for the receptor and
256  the ligand
257  @param h_receptor
258  @param h_ligand
259  """
260  self.h_receptor = h_receptor
261  self.h_ligand = h_ligand
262 
263  def set_rigid_bodies(self, rb_receptor, rb_ligand):
264  """
265  Sets the rigid bodies (core.RigidBody objects) for the receptor and
266  the ligand
267  @param rb_receptor
268  @param rb_ligand
269  """
270  self.rb_receptor = rb_receptor
271  self.rb_ligand = rb_ligand
272 
273  def move_ligand(self):
274  """
275  Movest the ligand close to the receptor based on the xlinks
276  provided by set_xlinks()
277  """
278  log.debug("Moving ligand close to the receptor using the xlinks")
279  n = len(self.xlinks_list)
280  if n == 1:
281  self.move_one_xlink()
282  elif n >= 2:
283  self.move_xlinks()
284 
285  def get_residue_particle(self, h, ch, res):
286  """
287  Get the particle representing a residue in a hierarchy
288  @param h atom.Hierarchy containing the residue
289  @param ch The chain id
290  @param res index of the residue
291  """
292  s = IMP.atom.Selection(h, chain=ch, residue_index=res)
293  return s.get_selected_particles()[0]
294 
295  def get_residue_coordinates(self, h, ch, res):
296  """
297  Get the coordinates for a residue in a molecular hierarchy
298  @param h atom.Hierarchy object
299  @param ch The chain id
300  @param res Residue index
301  """
302  p = self.get_residue_particle(h, ch, res)
303  return core.XYZ(p).get_coordinates()
304 
305  def write_ligand(self, fn):
306  """
307  Write a pdb file the coordinates of the ligand
308  @param fn
309  """
310  atom.write_pdb(self.h_ligand, fn)
311 
312  def move_one_xlink(self):
313  """
314  Put the residues in a random distance between 0 and the maximum
315  cross-linkin distance
316  """
317  xl = self.xlinks_list[0]
318  center = self.get_residue_coordinates(self.h_receptor, xl.first_chain,
319  xl.first_residue)
320  sph = alg.Sphere3D(center, xl.distance)
321  v = alg.get_random_vector_in(sph)
322  ref = self.rb_ligand.get_reference_frame()
323  coords = ref.get_transformation_to().get_translation()
324  R = ref.get_transformation_to().get_rotation()
325  lig = self.get_residue_coordinates(self.h_ligand, xl.second_chain,
326  xl.second_residue)
327  log.debug("Ligand residue before moving %s", lig)
328  displacement = v - lig
329  T = alg.Transformation3D(R, coords + displacement)
330  self.rb_ligand.set_reference_frame(alg.ReferenceFrame3D(T))
331  new_coords = self.get_residue_coordinates(
332  self.h_ligand, xl.second_chain,
333  xl.second_residue)
334  log.debug("ligand after moving %s", new_coords)
335 
336  def move_xlinks(self, ):
337  """
338  Function equivalent to move_one_xlink() for the case where there
339  are more than one cross-link restraints available.
340  Puts the ligand residues as close as possible to the receptor
341  residues
342  """
343  rec_coords = []
344  lig_coords = []
345  for xl in self.xlinks_list:
346  c = self.get_residue_coordinates(self.h_receptor, xl.first_chain,
347  xl.first_residue)
348  rec_coords.append(c)
349  c = self.get_residue_coordinates(self.h_ligand, xl.second_chain,
350  xl.second_residue)
351  lig_coords.append(c)
352  log.debug("Receptor residues before moving %s", rec_coords)
353  log.debug("Ligand residues before moving %s", lig_coords)
354  ref = self.rb_ligand.get_reference_frame()
355  Tr = alg.get_transformation_aligning_first_to_second(lig_coords,
356  rec_coords)
357  T = ref.get_transformation_to()
358  newT = alg.compose(Tr, T)
359  self.rb_ligand.set_reference_frame(alg.ReferenceFrame3D(newT))
360 
361  moved_lig_coords = []
362  for xl in self.xlinks_list:
363  c = self.get_residue_coordinates(self.h_ligand, xl.second_chain,
364  xl.second_residue)
365  moved_lig_coords.append(c)
366  log.debug("Ligand residues after moving %s", moved_lig_coords)
Class for storing model, its restraints, constraints, and particles.
Definition: Model.h:86
Select all non-alternative ATOM records.
Definition: pdb.h:128
A decorator for a particle with x,y,z coordinates.
Definition: XYZ.h:30
std::ostream & show(Hierarchy h, std::ostream &out=std::cout)
Print the hierarchy using a given decorator to display each node.
Basic functionality that is expected to be used by a wide variety of IMP users.
General purpose algebraic and geometric methods that are expected to be used by a wide variety of IMP...
Functionality for loading, creating, manipulating and scoring atomic structures.
Select hierarchy particles identified by the biological name.
Definition: Selection.h:70