IMP logo
IMP Reference Guide  develop.6f18bfa751,2025/09/20
The Integrative Modeling Platform
xlink.py
1 import typing
2 import json
3 import pathlib
4 
5 import IMP
6 import IMP.core
7 import IMP.atom
9 import IMP.rmf
10 import IMP.bff
11 
12 import numpy as np
13 
14 
15 def get_obstacles(*args, **keys):
16  raise NotImplementedError()
17 
18 
19 def get_path_length(*args, **keys):
20  raise NotImplementedError()
21 
22 
23 class XLinkScore(typing.TypedDict):
24  """
25  Attributes
26  ----------
27 
28  individual: list
29  The scores to all potential cross linking partners. The score is the
30  fraction of distances of all path that are shorter or equal to the
31  cross-linker length
32 
33  total: np.ndarray
34  The sum of scores to all potential cross linking partners.
35  The score is the fraction of distances of all path that are
36  shorter or equal to the cross-linker length
37 
38  """
39  individual: list
40  total: np.ndarray
41 
42 
43 class ScoreXlinkSurfaceDistance(IMP.pmi.restraints.RestraintBase):
44  """
45  :param linker_length:
46  :param linker_width:
47  :param radius:
48  :param simulation_grid_spacing:
49  :param min_points: the minimum number of points the the computed volume. If
50  there are less points the site is not accessible and the score will be
51  zero
52  :param verbose:
53  :return:
54  """
55 
56  model: IMP.Model
57  obstacles: np.ndarray
58  xlinks: typing.Dict[int, typing.Dict]
59  linker_length: float = 20.0
60  linker_width: float = 2.0
61  radius: float = 1.0
62  simulation_grid_spacing: float = 3.5
63  min_points: int = 100
64  verbose: bool = False
65  name_map: dict = None
66 
67  def compute_scores(self, **kwargs) -> XLinkScore:
68  linker_length = kwargs.get('linker_length', self.linker_length)
69  linker_width = kwargs.get('linker_width', self.linker_width)
70  radius = kwargs.get('radius', self.radius)
71  simulation_grid_spacing = kwargs.get(
72  'simulation_grid_spacing', self.simulation_grid_spacing)
73  verbose = kwargs.get('verbose', self.verbose)
74  min_points = kwargs.get('min_points', self.min_points)
75  obstacles = self.obstacles
76  scores = list()
77  total_scores = list()
78  for xlink_key in self.xlinks:
79  xlink = self.xlinks[xlink_key]
80  protein_1 = xlink['protein_1']
81  protein_2 = xlink['protein_2']
82  residue_1 = xlink['residue_1']
83  residue_2 = xlink['residue_2']
84  attachment_idx_1 = np.where(
85  (obstacles['res_id'] == residue_1)
86  & (obstacles['protein_name'] == protein_1))[0]
87  attachment_idx_2 = np.where(
88  (obstacles['res_id'] == residue_2)
89  & (obstacles['protein_name'] == protein_2))[0]
90  x_links_scores = []
91  for idx_1 in attachment_idx_1:
92  origin = obstacles['xyz'][idx_1]
93  for idx_2 in attachment_idx_2:
94  target = obstacles['xyz'][idx_2]
95  if np.linalg.norm(origin - target) > linker_length:
96  if verbose:
97  print("Eucledian distance > linker length")
98  x_links_scores.append(0.0)
99  else:
100  av = get_path_length(
101  obstacles=self.obstacles,
102  idx=idx_1,
103  linker_length=linker_length,
104  linker_width=linker_width,
105  radius=radius,
106  simulation_grid_spacing=simulation_grid_spacing
107  )
108  if len(np.array(av.points()).T) < min_points:
109  if verbose:
110  print(protein_1, residue_1,
111  "is not accessible")
112  x_links_scores.append(0.0)
113  else:
114  if verbose:
115  print("Eucledian distance < linker length.. "
116  "Testing surface distance")
117  points = av.points()
118  xyz = points[0:3]
119  dist = points[3]
120  dist_eq = np.linalg.norm(
121  xyz.T - target, axis=1) + dist
122  shorter = np.sum(
123  dist_eq < (linker_length + radius))
124  score = shorter / len(dist_eq)
125  x_links_scores.append(score)
126  indiviudal_scores = np.array(x_links_scores)
127  scores.append(indiviudal_scores)
128  total_score = indiviudal_scores.sum()
129  total_scores.append(total_score)
130  if verbose:
131  print(xlink_key, ":", xlink, "score:", total_score)
132  re: XLinkScore = {
133  'total': np.array(total_scores, dtype=np.float),
134  'individual': scores
135  }
136  return re
137 
138  def __init__(
139  self,
140  root_hier: IMP.atom.Hierarchy = None,
141  verbose: bool = False,
142  xlink_settings_file: str = '',
143  rmf_file: str = '',
144  weight: float = 1.0,
145  label: str = 'XLinkSurfaceDistance'
146  ):
147  # create a new ScoreXlinkRMF object with predefined settings
148  base_dir = pathlib.Path(xlink_settings_file).parent
149  with open(xlink_settings_file, 'r') as fp:
150  xlink_settings = json.load(fp)
151  if root_hier is None:
152  model = IMP.Model()
153  else:
154  model = root_hier.get_model()
155  super().__init__(
156  model,
157  weight=weight,
158  label=label
159  )
160  self.model = model
161  keys, formats = list(zip(*OBSTACLES_KEYS_FORMATS)) # noqa: F821
162  obstacles = np.zeros(0, dtype={
163  'names': keys,
164  'formats': formats
165  }
166  )
167  self.obstacles = obstacles
168  self.xlinks = dict()
169  self.linker_length = xlink_settings['linker_length']
170  self.linker_width = xlink_settings['linker_width']
171  self.radius = xlink_settings['radius']
172  self.simulation_grid_spacing = \
173  xlink_settings['simulation_grid_spacing']
174  self.min_points = xlink_settings['min_points']
175  self.verbose = verbose
176  xlink_file = xlink_settings['xlink_file']
177  if pathlib.Path(xlink_file).is_file():
178  self.xlinks = IMP.bff.tools.read_xlink_table(
179  fn=xlink_file
180  )
181  elif (base_dir / xlink_file).is_file():
182  self.xlinks = IMP.bff.tools.read_xlink_table(
183  fn=str(base_dir / xlink_file)
184  )
185  else:
186  raise FileNotFoundError("Could not find XL file %s" % xlink_file)
187  if pathlib.Path(rmf_file).is_file():
188  self.obstacles = get_obstacles(
189  rmf_file=rmf_file,
190  model=self.model,
191  name_map=self.name_map
192  )
193  # Add custom metadata (will be saved in RMF output)
194  self.rs.filename = xlink_settings_file
195 
196  def __call__(
197  self,
198  rmf_file: str = '',
199  xlink_file: str = '',
200  frame_index: int = 0,
201  hier: IMP.atom.Hierarchy = None,
202  *args, **kwargs
203  ):
204  if pathlib.Path(xlink_file).is_file():
205  self.xlinks = IMP.bff.tools.read_xlink_table(
206  fn=xlink_file
207  )
208  if pathlib.Path(rmf_file).is_file():
209  self.obstacles = get_obstacles(
210  rmf_file=rmf_file,
211  hier=hier,
212  model=self.model,
213  frame_index=frame_index,
214  name_map=self.name_map
215  )
216  return self.compute_scores(**kwargs)
217 
218  def get_score(self) -> float:
219  total_score = float(np.sum(self()['total']))
220  return total_score
def read_xlink_table
Read a xlink table.
Class for storing model, its restraints, constraints, and particles.
Definition: Model.h:86
Classes to handle different kinds of restraints.
The standard decorator for manipulating molecular structures.
Basic functionality that is expected to be used by a wide variety of IMP users.
Functionality for loading, creating, manipulating and scoring atomic structures.
Support for the RMF file format for storing hierarchical molecular data and markup.
Bayesian Fluorescence Framework.