IMP logo
IMP Reference Guide  develop.37630189d9,2026/04/22
The Integrative Modeling Platform
crosslinking.py
1 """@namespace IMP.pmi.restraints.crosslinking
2 Restraints for handling cross-linking data.
3 """
4 
5 import IMP
6 import IMP.core
7 import IMP.algebra
8 import IMP.atom
9 import IMP.isd
10 import IMP.container
11 import IMP.pmi.tools
12 import IMP.pmi.output
14 import IMP.pmi.restraints
15 from math import log
16 from collections import defaultdict
17 import itertools
18 import operator
19 import warnings
20 
21 
22 class CrossLinkingMassSpectrometryRestraint(IMP.pmi.restraints.RestraintBase):
23  """Setup cross-link distance restraints from mass spectrometry data.
24  The noise in the data and the structural uncertainty of cross-linked
25  amino-acids is inferred using Bayes theory of probability
26  @note Wraps an IMP::isd::CrossLinkMSRestraint
27  """
28  _include_in_rmf = True
29 
30  def __init__(self, root_hier, database=None, length=10.0, resolution=None,
31  slope=0.02, label=None, filelabel="None",
32  attributes_for_label=None, linker=None, weight=1.):
33  """Constructor.
34  @param root_hier The canonical hierarchy containing all the states
35  @param database The IMP.pmi.io.crosslink.CrossLinkDataBase
36  object that contains the cross-link dataset
37  @param length maximal cross-linker length (including the residue
38  sidechains)
39  @param resolution what representation resolution should the cross-link
40  restraint be applied to.
41  @param slope The slope of a distance-linear scoring function that
42  funnels the score when the particles are
43  too far away. Suggested value 0.02.
44  @param label the extra text to label the restraint so that it is
45  searchable in the output
46  @param filelabel automatically generated file containing
47  missing/included/excluded
48  cross-links will be labeled using this text
49  @param attributes_for_label
50  @param weight Weight of restraint
51  @param linker description of the chemistry of the linker itself, as
52  an ihm.ChemDescriptor object
53  (see https://python-ihm.readthedocs.io/en/latest/main.html#ihm.ChemDescriptor).
54  Common cross-linkers can be found in the `ihm.cross_linkers`
55  module.
56  """ # noqa: E501
57 
58  model = root_hier.get_model()
59 
60  super().__init__(
61  model, weight=weight, label=label,
62  restraint_set_class=IMP.pmi.CrossLinkRestraintSet)
63 
64  if database is None:
65  raise Exception("You must pass a database")
66  if not isinstance(database, IMP.pmi.io.crosslink.CrossLinkDataBase):
67  raise TypeError(
68  "CrossLinkingMassSpectrometryRestraint: database should "
69  "be an IMP.pmi.io.crosslink.CrossLinkDataBase object")
70  self.database = database
71 
72  if resolution == 0 or resolution is None:
73  raise Exception("You must pass a resolution and it can't be zero")
74 
75  indb = open("included." + filelabel + ".xl.db", "w")
76  exdb = open("excluded." + filelabel + ".xl.db", "w") # noqa: F841
77  midb = open("missing." + filelabel + ".xl.db", "w")
78 
79  self.linker = linker
80  if linker is None:
81  raise ValueError(
82  "No linker chemistry specified. A linker must be given, as an "
83  "ihm.ChemDescriptor object (see the "
84  "CrossLinkingMassSpectrometryRestraint documentation).")
85  self.rs.set_name(self.rs.get_name() + "_Data")
86  self.rspsi = self._create_restraint_set("PriorPsi")
87  self.rssig = self._create_restraint_set("PriorSig")
88  self.rslin = self._create_restraint_set("Linear")
89  # Add custom metadata (will be saved in RMF output)
90  self.rs.set_metadata(self.database.name, length, slope)
91  if linker:
92  self.rs.set_linker_auth_name(linker.auth_name)
93  for attr in ('chemical_name', 'smiles', 'smiles_canonical',
94  'inchi', 'inchi_key'):
95  val = getattr(linker, attr)
96  if val:
97  getattr(self.rs, "set_linker_" + attr)(val)
98 
99  # dummy linear restraint used for Chimera display
100  self.linear = IMP.core.Linear(0, 0.0)
101  self.linear.set_slope(0.0)
102  dps2 = IMP.core.DistancePairScore(self.linear)
103 
104  self.psi_is_sampled = True
105  self.sigma_is_sampled = True
106  self.psi_dictionary = {}
107  self.sigma_dictionary = {}
108  self.xl_list = []
109  self.outputlevel = "low"
110 
111  restraints = []
112 
113  xl_groups = [p.get_cross_link_group(self)
114  for p, state in IMP.pmi.tools._all_protocol_outputs(
115  root_hier)]
116 
117  # first add all the molecule copies as clones to the database
118  copies_to_add = defaultdict(int)
119  print('gathering copies')
120  for xlid in self.database.xlid_iterator():
121  for xl in self.database[xlid]:
122  r1 = xl[self.database.residue1_key]
123  c1 = xl[self.database.protein1_key]
124  r2 = xl[self.database.residue2_key]
125  c2 = xl[self.database.protein2_key]
126  for c, r in ((c1, r1), (c2, r2)):
127  if c in copies_to_add:
128  continue
129  sel = IMP.atom.Selection(
130  root_hier, state_index=0, molecule=c, residue_index=r,
131  resolution=resolution).get_selected_particles()
132  if len(sel) > 0:
133  copies_to_add[c] = len(sel)-1
134  print(copies_to_add)
135  for molname in copies_to_add:
136  if copies_to_add[molname] == 0:
137  continue
139  self.database.protein1_key, operator.eq, molname)
140  self.database.set_value(self.database.protein1_key,
141  molname + '.0', fo1)
143  self.database.protein2_key, operator.eq, molname)
144  self.database.set_value(self.database.protein2_key,
145  molname + '.0', fo2)
146  for ncopy in range(copies_to_add[molname]):
147  self.database.clone_protein('%s.0' % molname,
148  '%s.%i' % (molname, ncopy + 1))
149  print('done pmi2 prelims')
150 
151  for xlid in self.database.xlid_iterator():
152  new_contribution = True
153  for xl in self.database[xlid]:
154 
155  r1 = xl[self.database.residue1_key]
156  c1 = xl[self.database.protein1_key]
157  r2 = xl[self.database.residue2_key]
158  c2 = xl[self.database.protein2_key]
159 
160  name1 = c1
161  name2 = c2
162  copy1 = 0
163  copy2 = 0
164  if '.' in c1:
165  name1, copy1 = c1.split('.')
166  if '.' in c2:
167  name2, copy2 = c2.split('.')
168 
169  # todo: check that offset is handled correctly
170  ex_xls = [
171  (p[0].add_experimental_cross_link(r1, name1, r2, name2,
172  group), group)
173  for p, group in zip(
174  IMP.pmi.tools._all_protocol_outputs(root_hier),
175  xl_groups)]
176 
177  iterlist = range(len(IMP.atom.get_by_type(
178  root_hier, IMP.atom.STATE_TYPE)))
179  for nstate, r in enumerate(iterlist):
180  # loop over every state
181  xl[self.database.state_key] = nstate
182  xl[self.database.data_set_name_key] = self.label
183 
184  ps1 = IMP.atom.Selection(
185  root_hier, state_index=nstate, molecule=name1,
186  copy_index=int(copy1), residue_index=r1,
187  resolution=resolution).get_selected_particles()
188  ps2 = IMP.atom.Selection(
189  root_hier, state_index=nstate, molecule=name2,
190  copy_index=int(copy2), residue_index=r2,
191  resolution=resolution).get_selected_particles()
192 
193  ps1 = [IMP.atom.Hierarchy(p) for p in ps1]
194  ps2 = [IMP.atom.Hierarchy(p) for p in ps2]
195 
196  if len(ps1) > 1:
197  raise ValueError(
198  "residue %d of chain %s selects multiple "
199  "particles %s" % (r1, c1, str(ps1)))
200  elif len(ps1) == 0:
201  warnings.warn(
202  "CrossLinkingMassSpectrometryRestraint: "
203  "residue %d of chain %s is not there" % (r1, c1),
205  midb.write(str(xl) + "\n")
206  continue
207 
208  if len(ps2) > 1:
209  raise ValueError(
210  "residue %d of chain %s selects multiple "
211  "particles %s" % (r2, c2, str(ps2)))
212  elif len(ps2) == 0:
213  warnings.warn(
214  "CrossLinkingMassSpectrometryRestraint: "
215  "residue %d of chain %s is not there" % (r2, c2),
217  midb.write(str(xl) + "\n")
218  continue
219 
220  p1 = ps1[0]
221  p2 = ps2[0]
222 
223  if p1 == p2 and r1 == r2:
224  warnings.warn(
225  "CrossLinkingMassSpectrometryRestraint: "
226  "same particle and same residue, skipping "
227  "cross-link", IMP.pmi.StructureWarning)
228  continue
229 
230  if new_contribution:
231  print("generating a new cross-link restraint")
232  new_contribution = False
234  self.model,
235  length,
236  slope)
237  dr.set_source_protein1(c1)
238  dr.set_source_protein2(c2)
239  dr.set_source_residue1(r1)
240  dr.set_source_residue2(r2)
241  restraints.append(dr)
242 
243  if self.database.sigma1_key not in xl.keys():
244  sigma1name = "SIGMA"
245  xl[self.database.sigma1_key] = sigma1name
246  else:
247  sigma1name = xl[self.database.sigma1_key]
248  sigma1 = self.create_sigma(sigma1name)
249 
250  if self.database.sigma2_key not in xl.keys():
251  sigma2name = "SIGMA"
252  xl[self.database.sigma2_key] = sigma2name
253  else:
254  sigma2name = xl[self.database.sigma2_key]
255  sigma2 = self.create_sigma(sigma2name)
256 
257  if self.database.psi_key not in xl.keys():
258  psiname = "PSI"
259  xl[self.database.psi_key] = psiname
260  else:
261  psiname = xl[self.database.psi_key]
262 
263  psi = self.create_psi(psiname)
264 
265  p1i = p1.get_particle_index()
266  xl["Particle1"] = p1
267  p2i = p2.get_particle_index()
268  xl["Particle2"] = p2
269  s1i = sigma1.get_particle().get_index()
270  xl["Particle_sigma1"] = sigma1
271  s2i = sigma2.get_particle().get_index()
272  xl["Particle_sigma2"] = sigma2
273  psii = psi.get_particle().get_index()
274  xl["Particle_psi"] = psi
275 
276  dr.add_contribution((p1i, p2i), (s1i, s2i), psii)
277  xl["Restraint"] = dr
278 
279  print("--------------")
280  print("CrossLinkingMassSpectrometryRestraint: generating "
281  "cross-link restraint between")
282  print("CrossLinkingMassSpectrometryRestraint: residue %d "
283  "of chain %s and residue %d of chain %s"
284  % (r1, c1, r2, c2))
285  print("CrossLinkingMassSpectrometryRestraint: with sigma1 "
286  "%s sigma2 %s psi %s"
287  % (sigma1name, sigma2name, psiname))
288  print("CrossLinkingMassSpectrometryRestraint: between "
289  "particles %s and %s"
290  % (p1.get_name(), p2.get_name()))
291  print("==========================================\n")
292  for p, ex_xl in zip(IMP.pmi.tools._all_protocol_outputs(
293  root_hier),
294  ex_xls):
295  p[0].add_cross_link(p[1], ex_xl[0], p1, p2, length,
296  sigma1, sigma2, psi, ex_xl[1])
297 
298  # check if the two residues belong to the same rigid body
301  IMP.core.RigidMember(p1).get_rigid_body() ==
302  IMP.core.RigidMember(p2).get_rigid_body()):
303  xl["IntraRigidBody"] = True
304  else:
305  xl["IntraRigidBody"] = False
306 
307  xl_label = self.database.get_short_cross_link_string(xl)
308  xl["ShortLabel"] = xl_label
309  dr.set_name(xl_label)
310 
311  if p1i != p2i:
312  pr = IMP.core.PairRestraint(self.model, dps2,
313  (p1i, p2i))
314  pr.set_name(xl_label)
315  self.rslin.add_restraint(pr)
316 
317  self.xl_list.append(xl)
318 
319  indb.write(str(xl) + "\n")
320 
321  if len(self.xl_list) == 0:
322  raise SystemError(
323  "CrossLinkingMassSpectrometryRestraint: no cross-link "
324  "was constructed")
325  self.xl_restraints = restraints
326  lw = IMP.isd.LogWrapper(restraints, 1.0)
327  self.rs.add_restraint(lw)
328  indb.close()
329  exdb.close()
330  midb.close()
331 
332  def __set_dataset(self, ds):
333  self.database.dataset = ds
334  dataset = property(lambda self: self.database.dataset, __set_dataset)
335 
336  def get_hierarchies(self):
337  """ get the hierarchy """
338  return self.prot
339 
341  """ get the restraint set """
342  return self.rs
343 
344  def get_restraints(self):
345  """ get the restraints in a list """
346  return self.xl_restraints
347 
349  """ get the dummy restraints to be displayed in the rmf file """
350  return self.rs, self.rssig, self.rspsi
351 
353  """ Get a list of tuples containing the particle pairs """
354  ppairs = []
355  for i in range(len(self.pairs)):
356  p0 = self.pairs[i][0]
357  p1 = self.pairs[i][1]
358  ppairs.append((p0, p1))
359  return ppairs
360 
361  def set_output_level(self, level="low"):
362  """ Set the output level of the output """
363  self.outputlevel = level
364 
365  def set_psi_is_sampled(self, is_sampled=True):
366  """ Switch on/off the sampling of psi particles """
367  self.psi_is_sampled = is_sampled
368 
369  def set_sigma_is_sampled(self, is_sampled=True):
370  """ Switch on/off the sampling of sigma particles """
371  self.sigma_is_sampled = is_sampled
372 
373  def create_sigma(self, name):
374  """ This is called internally. Creates a nuisance
375  on the structural uncertainty """
376  if name in self.sigma_dictionary:
377  return self.sigma_dictionary[name][0]
378 
379  sigmainit = 2.0
380  sigmaminnuis = 0.0000001
381  sigmamaxnuis = 1000.0
382  sigmamin = 0.01
383  sigmamax = 100.0
384  sigmatrans = 0.5
385  sigma = IMP.pmi.tools.SetupNuisance(
386  self.model, sigmainit, sigmaminnuis, sigmamaxnuis,
387  self.sigma_is_sampled, name=name).get_particle()
388  self.sigma_dictionary[name] = (
389  sigma,
390  sigmatrans,
391  self.sigma_is_sampled)
392  self.rssig.add_restraint(
394  self.model,
395  sigma,
396  1000000000.0,
397  sigmamax,
398  sigmamin))
399  return sigma
400 
401  def create_psi(self, name):
402  """ This is called internally. Creates a nuisance
403  on the data uncertainty """
404  if name in self.psi_dictionary:
405  return self.psi_dictionary[name][0]
406 
407  psiinit = 0.25
408  psiminnuis = 0.0000001
409  psimaxnuis = 0.4999999
410  psimin = 0.01
411  psimax = 0.49
412  psitrans = 0.1
413  psi = IMP.pmi.tools.SetupNuisance(
414  self.model, psiinit, psiminnuis, psimaxnuis,
415  self.psi_is_sampled, name=name).get_particle()
416  self.psi_dictionary[name] = (psi, psitrans, self.psi_is_sampled)
417 
418  self.rspsi.add_restraint(
419  IMP.isd.UniformPrior(self.model, psi, 1000000000.0,
420  psimax, psimin))
421 
422  self.rspsi.add_restraint(IMP.isd.JeffreysRestraint(self.model, psi))
423  return psi
424 
425  def get_output(self):
426  """Get the output of the restraint to be used by the IMP.pmi.output
427  object"""
428  # todo: return a callable here instead
429  output = super().get_output()(None)
430 
431  for xl in self.xl_list:
432 
433  xl_label = xl["ShortLabel"]
434  ln = xl["Restraint"]
435  p0 = xl["Particle1"]
436  p1 = xl["Particle2"]
437  output["CrossLinkingMassSpectrometryRestraint_Score_" +
438  xl_label] = str(-log(ln.unprotected_evaluate(None)))
439 
440  d0 = IMP.core.XYZ(p0)
441  d1 = IMP.core.XYZ(p1)
442  output["CrossLinkingMassSpectrometryRestraint_Distance_" +
443  xl_label] = str(IMP.core.get_distance(d0, d1))
444 
445  for psiname in self.psi_dictionary:
446  output["CrossLinkingMassSpectrometryRestraint_Psi_" +
447  str(psiname) + self._label_suffix] = str(
448  self.psi_dictionary[psiname][0].get_scale())
449 
450  for sigmaname in self.sigma_dictionary:
451  output["CrossLinkingMassSpectrometryRestraint_Sigma_" +
452  str(sigmaname) + self._label_suffix] = str(
453  self.sigma_dictionary[sigmaname][0].get_scale())
454 
455  return output
456 
457  def get_likelihood(self):
458  """Get the unweighted likelihood of the restraint"""
459  likelihood = 1
460  for restraint in self.xl_restraints:
461  likelihood *= restraint.get_probability()
462 
463  return likelihood
464 
465  def get_movers(self):
466  """ Get all need data to construct a mover in IMP.pmi.dof class"""
467  movers = []
468  if self.sigma_is_sampled:
469  for sigmaname in self.sigma_dictionary:
470  mover_name = \
471  "Nuisances_CrossLinkingMassSpectrometryRestraint_Sigma_" \
472  + str(sigmaname) + "_" + self.label
473  particle = self.sigma_dictionary[sigmaname][0]
474  maxstep = (self.sigma_dictionary[sigmaname][1])
476  [particle], IMP.FloatKeys([IMP.FloatKey("nuisance")]),
477  maxstep)
478  mv.set_name(mover_name)
479  movers.append(mv)
480 
481  if self.psi_is_sampled:
482  for psiname in self.psi_dictionary:
483  mover_name = \
484  "Nuisances_CrossLinkingMassSpectrometryRestraint_Psi_" + \
485  str(psiname) + "_" + self.label
486  particle = self.psi_dictionary[psiname][0]
487  maxstep = (self.psi_dictionary[psiname][1])
489  [particle], IMP.FloatKeys([IMP.FloatKey("nuisance")]),
490  maxstep)
491  mv.set_name(mover_name)
492  movers.append(mv)
493 
494  return movers
495 
497  """ Get the particles to be sampled by the IMP.pmi.sampler object """
498  ps = {}
499  if self.sigma_is_sampled:
500  for sigmaname in self.sigma_dictionary:
501  ps["Nuisances_CrossLinkingMassSpectrometryRestraint_Sigma_" +
502  str(sigmaname) + self._label_suffix] =\
503  ([self.sigma_dictionary[sigmaname][0]],
504  self.sigma_dictionary[sigmaname][1])
505 
506  if self.psi_is_sampled:
507  for psiname in self.psi_dictionary:
508  ps["Nuisances_CrossLinkingMassSpectrometryRestraint_Psi_" +
509  str(psiname) + self._label_suffix] = \
510  ([self.psi_dictionary[psiname][0]],
511  self.psi_dictionary[psiname][1])
512 
513  return ps
514 
515 
516 class AtomicCrossLinkMSRestraint(IMP.pmi.restraints.RestraintBase):
517  """Setup cross-link distance restraints at atomic level
518  The "atomic" aspect is that it models the particle uncertainty with
519  a Gaussian. The noise in the data and the structural uncertainty of
520  cross-linked amino-acids is inferred using Bayes' theory of probability
521  @note Wraps an IMP::isd::AtomicCrossLinkMSRestraint
522  """
523 
524  _include_in_rmf = True
525 
526  def __init__(self, root_hier, xldb, atom_type="NZ", length=10.0,
527  slope=0.01, nstates=None, label=None,
528  nuisances_are_optimized=True, sigma_init=5.0,
529  psi_init=0.01, one_psi=True, filelabel=None, weight=1.):
530  """Constructor.
531  Automatically creates one "sigma" per cross-linked residue and one
532  "psis" per pair.
533  Other nuisance options are available.
534  @note Will return an error if the data+extra_sel don't specify
535  two particles per XL pair.
536  @param root_hier The root hierarchy on which you'll do selection
537  @param xldb CrossLinkDataBase object
538  @param atom_type Can either be "NZ" or "CA"
539  @param length The XL linker length
540  @param slope Linear term to add to the restraint function to
541  help when far away
542  @param nstates The number of states to model. Defaults to the
543  number of states in root.
544  @param label The output label for the restraint
545  @param nuisances_are_optimized Whether to optimize nuisances
546  @param sigma_init The initial value for all the sigmas
547  @param psi_init The initial value for all the psis
548  @param one_psi Use a single psi for all restraints (if False,
549  creates one per XL)
550  @param filelabel automatically generated file containing
551  missing/included/excluded
552  cross-links will be labeled using this text
553  @param weight Weight of restraint
554 
555  """
556 
557  # basic params
558  self.root = root_hier
559  rname = "AtomicXLRestraint"
560  super().__init__(
561  self.root.get_model(), name="AtomicXLRestraint", label=label,
562  weight=weight)
563  self.xldb = xldb
564  self.length = length
565  self.sigma_is_sampled = nuisances_are_optimized
566  self.psi_is_sampled = nuisances_are_optimized
567 
568  if atom_type not in ("NZ", "CA"):
569  raise Exception("AtomicXLRestraint: atom_type must be NZ or CA")
570  self.atom_type = atom_type
571  self.nstates = nstates
572  if nstates is None:
573  self.nstates = len(IMP.atom.get_by_type(self.root,
574  IMP.atom.STATE_TYPE))
575  elif nstates != len(IMP.atom.get_by_type(self.root,
576  IMP.atom.STATE_TYPE)):
577  print("Warning: nstates is not the same as the number of states "
578  "in root")
579 
580  self.rs_psi = self._create_restraint_set("psi")
581  self.rs_sig = self._create_restraint_set("sigma")
582  self.rs_lin = self._create_restraint_set("linear")
583 
584  self.psi_dictionary = {}
585  self.sigma_dictionary = {}
586 
587  self.particles = defaultdict(set)
588  self.one_psi = one_psi
589  self.bonded_pairs = []
590  if self.one_psi:
591  print('creating a single psi for all XLs')
592  else:
593  print('creating one psi for each XL')
594 
595  # output logging file
596  if filelabel is not None:
597  indb = open("included." + filelabel + ".xl.db", "w") # noqa: F841
598  exdb = open("excluded." + filelabel + ".xl.db", "w") # noqa: F841
599  midb = open("missing." + filelabel + ".xl.db", "w")
600 
601  # Setup nuisances
602  self._create_sigma('sigma', sigma_init)
603  if one_psi:
604  self._create_psi('psi', psi_init)
605  else:
606  for xlid in self.xldb.xlid_iterator():
607  self._create_psi(xlid, psi_init)
608 
609  # create all the XLs
610  xlrs = []
611  for xlid in self.xldb.xlid_iterator():
612  # create restraint for this data point
613  if one_psi:
614  psip = self.psi_dictionary['psi'][0].get_particle_index()
615  else:
616  psip = self.psi_dictionary[
617  unique_id][0].get_particle_index() # noqa: F821
618  r = IMP.isd.AtomicCrossLinkMSRestraint(self.model,
619  self.length,
620  psip,
621  slope,
622  True)
623  num_contributions = 0
624 
625  # add a contribution for each XL ambiguity option within each state
626  for nstate in self.nstates:
627  for xl in self.xldb[xlid]:
628  r1 = xl[self.xldb.residue1_key]
629  c1 = xl[self.xldb.protein1_key].strip()
630  r2 = xl[self.xldb.residue2_key]
631  c2 = xl[self.xldb.protein2_key].strip()
632 
633  # perform selection. these may contain multiples if
634  # Copies are used
635  ps1 = IMP.atom.Selection(
636  self.root, state_index=nstate,
637  atom_type=IMP.atom.AtomType(self.atom_type),
638  molecule=c1,
639  residue_index=r1).get_selected_particles()
640  ps2 = IMP.atom.Selection(
641  self.root, state_index=nstate,
642  atom_type=IMP.atom.AtomType(self.atom_type),
643  molecule=c2,
644  residue_index=r2).get_selected_particles()
645  if len(ps1) == 0:
646  warnings.warn("AtomicXLRestraint: residue %d of "
647  "chain %s is not there" % (r1, c1),
649  if filelabel is not None:
650  midb.write(str(xl) + "\n")
651  continue
652 
653  if len(ps2) == 0:
654  warnings.warn("AtomicXLRestraint: residue %d of "
655  "chain %s is not there" % (r2, c2),
657  if filelabel is not None:
658  midb.write(str(xl) + "\n")
659  continue
660 
661  # figure out sig1 and sig2 based on num XLs
662  '''
663  num1=num_xls_per_res[str(xl.r1)]
664  num2=num_xls_per_res[str(xl.r2)]
665  if num1<sig_threshold:
666  sig1=self.sig_low
667  else:
668  sig1=self.sig_high
669  if num2<sig_threshold:
670  sig2=self.sig_low
671  else:
672  sig2=self.sig_high
673  '''
674  sig1 = self.sigma_dictionary['sigma'][0]
675  sig2 = self.sigma_dictionary['sigma'][0]
676 
677  # add each copy contribution to restraint
678  for p1, p2 in itertools.product(ps1, ps2):
679  if p1 == p2:
680  continue
681  self.particles[nstate] |= set((p1, p2))
682  r.add_contribution(
683  [p1.get_index(), p2.get_index()],
684  [sig1.get_particle_index(),
685  sig2.get_particle_index()])
686  num_contributions += 1
687 
688  if num_contributions > 0:
689  print('XL:', xlid, 'num contributions:', num_contributions)
690  xlrs.append(r)
691  if len(xlrs) == 0:
692  raise Exception("You didn't create any XL restraints")
693  print('created', len(xlrs), 'XL restraints')
694  rname = self.rs.get_name()
695  self.rs = IMP.isd.LogWrapper(xlrs, self.weight)
696  self.rs.set_name(rname)
697  self.rs.set_weight(self.weight)
698  self.restraint_sets = [self.rs] + self.restraint_sets[1:]
699 
700  def get_hierarchy(self):
701  return self.prot
702 
703  def _create_sigma(self, name, sigmainit):
704  """ This is called internally. Creates a nuisance
705  on the structural uncertainty """
706  if name in self.sigma_dictionary:
707  return self.sigma_dictionary[name][0]
708 
709  sigmaminnuis = 0.0000001
710  sigmamaxnuis = 1000.0
711  sigmamin = 0.01
712  sigmamax = 100.0
713  sigmatrans = 0.5
714  sigma = IMP.pmi.tools.SetupNuisance(
715  self.model, sigmainit, sigmaminnuis, sigmamaxnuis,
716  self.sigma_is_sampled).get_particle()
717  self.sigma_dictionary[name] = (
718  sigma, sigmatrans, self.sigma_is_sampled)
719  self.rs_sig.add_restraint(IMP.isd.UniformPrior(
720  self.model, sigma, 1000000000.0, sigmamax, sigmamin))
721  return sigma
722 
723  def _create_psi(self, name, psiinit):
724  """ This is called internally. Creates a nuisance
725  on the data uncertainty """
726  if name in self.psi_dictionary:
727  return self.psi_dictionary[name][0]
728 
729  psiminnuis = 0.0000001
730  psimaxnuis = 0.4999999
731  psimin = 0.01
732  psimax = 0.49
733  psitrans = 0.1
734  psi = IMP.pmi.tools.SetupNuisance(self.model,
735  psiinit,
736  psiminnuis,
737  psimaxnuis,
738  self.psi_is_sampled).get_particle()
739  self.psi_dictionary[name] = (
740  psi,
741  psitrans,
742  self.psi_is_sampled)
743 
744  self.rs_psi.add_restraint(
746  self.model,
747  psi,
748  1000000000.0,
749  psimax,
750  psimin))
751 
752  self.rs_psi.add_restraint(IMP.isd.JeffreysRestraint(self.model, psi))
753  return psi
754 
756  """ create dummy harmonic restraints for each XL but don't add to model
757  Makes it easy to see each contribution to each XL in RMF
758  """
759  class MyGetRestraint:
760  def __init__(self, rs):
761  self.rs = rs
762 
763  def get_restraint_for_rmf(self):
764  return self.rs
765 
766  dummy_model = IMP.Model()
767  hps = IMP.core.HarmonicDistancePairScore(self.length, 1.0)
768  dummy_rs = []
769  for nxl in range(self.get_number_of_restraints()):
770  xl = IMP.isd.AtomicCrossLinkMSRestraint.get_from(
771  self.rs.get_restraint(nxl))
772  rs = IMP.RestraintSet(dummy_model, 'atomic_xl_'+str(nxl))
773  for ncontr in range(xl.get_number_of_contributions()):
774  ps = xl.get_contribution(ncontr)
776  hps, [self.model.get_particle(p) for p in ps],
777  'xl%i_contr%i' % (nxl, ncontr))
778  rs.add_restraint(dr)
779  dummy_rs.append(MyGetRestraint(rs))
780  return dummy_rs
781 
783  """ Get the particles to be sampled by the IMP.pmi.sampler object """
784  ps = {}
785  if self.sigma_is_sampled:
786  for sigmaname in self.sigma_dictionary:
787  ps["Nuisances_AtomicCrossLinkingMSRestraint_Sigma_" +
788  str(sigmaname) + self._label_suffix] = \
789  ([self.sigma_dictionary[sigmaname][0]],
790  self.sigma_dictionary[sigmaname][1])
791  if self.psi_is_sampled:
792  for psiname in self.psi_dictionary:
793  ps["Nuisances_CrossLinkingMassSpectrometryRestraint_Psi_" +
794  str(psiname) + self._label_suffix] = \
795  ([self.psi_dictionary[psiname][0]],
796  self.psi_dictionary[psiname][1])
797  return ps
798 
799  def get_bonded_pairs(self):
800  return self.bonded_pairs
801 
802  def get_number_of_restraints(self):
803  return self.rs.get_number_of_restraints()
804 
805  def __repr__(self):
806  return 'XL restraint with ' + \
807  str(len(self.rs.get_restraint(0).get_number_of_restraints())) \
808  + ' data points'
809 
810  def load_nuisances_from_stat_file(self, in_fn, nframe):
811  """Read a stat file and load all the sigmas.
812  This is potentially quite stupid.
813  It's also a hack since the sigmas should be stored in the RMF file.
814  Also, requires one sigma and one psi for ALL XLs.
815  """
816  import subprocess
817  sig_val = float(subprocess.check_output(
818  ["process_output.py", "-f", in_fn, "-s",
819  "AtomicXLRestraint_sigma"]).split('\n>')[1+nframe])
820  psi_val = float(subprocess.check_output(
821  ["process_output.py", "-f", in_fn, "-s",
822  "AtomicXLRestraint_psi"]).split('\n>')[1+nframe])
823  for nxl in range(self.rs.get_number_of_restraints()):
824  xl = IMP.isd.AtomicCrossLinkMSRestraint.get_from(
825  self.rs.get_restraint(nxl))
826  psip = xl.get_psi()
827  IMP.isd.Scale(self.model, psip).set_scale(psi_val)
828  for contr in range(xl.get_number_of_contributions()):
829  sig1, sig2 = xl.get_contribution_sigmas(contr)
830  IMP.isd.Scale(self.model, sig1).set_scale(sig_val)
831 
832  print('loaded nuisances from file')
833 
834  def plot_violations(self, out_prefix, max_prob_for_violation=0.1,
835  min_dist_for_violation=1e9, coarsen=False,
836  limit_to_chains=None, exclude_chains=''):
837  """Create CMM files, one for each state, of all cross-links.
838  will draw in GREEN if non-violated in all states (or if only one state)
839  will draw in PURPLE if non-violated only in a subset of states
840  (draws nothing elsewhere)
841  will draw in RED in ALL states if all violated
842  (if only one state, you'll only see green and red)
843 
844  @param out_prefix Output xlink files prefix
845  @param max_prob_for_violation It's a violation if the probability
846  is below this
847  @param min_dist_for_violation It's a violation if the min dist
848  is above this
849  @param coarsen Use CA positions
850  @param limit_to_chains Try to visualize just these chains
851  @param exclude_chains Try to NOT visualize these chains
852  """
853  print('going to calculate violations and plot CMM files')
854  all_stats = self.get_best_stats()
855  all_dists = [s["low_dist"] for s in all_stats]
856 
857  # prepare one output file per state
858  out_fns = []
859  out_nvs = []
860  state_info = []
861  cmds = defaultdict(set)
862  for nstate in range(self.nstates):
863  outf = open(out_prefix+str(nstate)+'.cmm', 'w')
864  outf.write('<marker_set name="xlinks_state%i"> \n' % nstate)
865  out_fns.append(outf)
866  out_nvs.append(0)
867  print('will limit to', limit_to_chains)
868  print('will exclude', exclude_chains)
869  state_info.append(self.get_best_stats(nstate,
870  limit_to_chains,
871  exclude_chains))
872 
873  for nxl in range(self.rs.get_number_of_restraints()):
874  # for this XL, check which states passed
875  npass = []
876  nviol = []
877  for nstate in range(self.nstates):
878  prob = state_info[nstate][nxl]["prob"]
879  low_dist = state_info[nstate][nxl]["low_dist"]
880  if (prob < max_prob_for_violation or
881  low_dist > min_dist_for_violation):
882  nviol.append(nstate)
883  else:
884  npass.append(nstate)
885 
886  # special coloring when all pass or all fail
887  all_pass = False
888  all_viol = False
889  if len(npass) == self.nstates:
890  all_pass = True
891  elif len(nviol) == self.nstates:
892  all_viol = True
893 
894  # finally, color based on above info
895  print(nxl, 'state dists:',
896  [state_info[nstate][nxl]["low_dist"]
897  for nstate in range(self.nstates)],
898  'viol states:', nviol, 'all viol?', all_viol)
899  for nstate in range(self.nstates):
900  if all_pass:
901  r = 0.365
902  g = 0.933
903  b = 0.365
904  continue
905  elif all_viol:
906  r = 0.980
907  g = 0.302
908  b = 0.247
909  continue
910  else:
911  if nstate in nviol:
912  continue
913  else:
914  r = 0.365
915  g = 0.933
916  b = 0.365
917  # now only showing if UNIQUELY PASSING in this state
918  pp = state_info[nstate][nxl]["low_pp"]
919  c1 = IMP.core.XYZ(self.model, pp[0]).get_coordinates()
920  c2 = IMP.core.XYZ(self.model, pp[1]).get_coordinates()
921 
923  IMP.atom.Atom(self.model, pp[0])).get_index()
924  ch1 = IMP.atom.get_chain_id(IMP.atom.Atom(self.model, pp[0]))
926  IMP.atom.Atom(self.model, pp[0])).get_index()
927  ch2 = IMP.atom.get_chain_id(IMP.atom.Atom(self.model, pp[0]))
928 
929  cmds[nstate].add((ch1, r1))
930  cmds[nstate].add((ch2, r2))
931 
932  outf = out_fns[nstate]
933  nv = out_nvs[nstate]
934  outf.write('<marker id= "%d" x="%.3f" y="%.3f" z="%.3f" '
935  'radius="0.8" r="%.2f" g="%.2f" b="%.2f"/> \n'
936  % (nv, c1[0], c1[1], c1[2], r, g, b))
937  outf.write('<marker id= "%d" x="%.3f" y="%.3f" z="%.3f" '
938  'radius="0.8" r="%.2f" g="%.2f" b="%.2f"/> \n'
939  % (nv+1, c2[0], c2[1], c2[2], r, g, b))
940  outf.write('<link id1= "%d" id2="%d" radius="0.8" '
941  'r="%.2f" g="%.2f" b="%.2f"/> \n'
942  % (nv, nv+1, r, g, b))
943  out_nvs[nstate] += 2
944 
945  for nstate in range(self.nstates):
946  out_fns[nstate].write('</marker_set>\n')
947  out_fns[nstate].close()
948  cmd = ''
949  for ch, r in cmds[nstate]:
950  cmd += '#%i:%i.%s ' % (nstate, r, ch)
951  print(cmd)
952 
953  return all_dists
954 
955  def _get_contribution_info(self, xl, ncontr, use_CA=False):
956  """Return the particles at that contribution. If requested will
957  return CA's instead"""
958  idx1 = xl.get_contribution(ncontr)[0]
959  idx2 = xl.get_contribution(ncontr)[1]
960  if use_CA:
961  sel = IMP.atom.Selection(
962  IMP.atom.get_residue(IMP.atom.Atom(self.model, idx1)),
963  atom_type=IMP.atom.AtomType("CA"))
964  idx1 = sel.get_selected_particle_indexes()[0]
965  sel = IMP.atom.Selection(
966  IMP.atom.get_residue(IMP.atom.Atom(self.model, idx2)),
967  atom_type=IMP.atom.AtomType("CA"))
968  idx2 = sel.get_selected_particle_indexes()[0]
970  IMP.core.XYZ(self.model, idx1).get_coordinates(),
971  IMP.core.XYZ(self.model, idx2).get_coordinates())
972  return idx1, idx2, dist
973 
974  def get_best_stats(self, limit_to_state=None, limit_to_chains=None,
975  exclude_chains='', use_CA=False):
976  ''' return the probability, best distance, two coords, and possibly
977  the psi for each xl
978  @param limit_to_state Only examine contributions from one state
979  @param limit_to_chains Returns the particles for certain "easy
980  to visualize" chains
981  @param exclude_chains Even if you limit, don't let one end be in
982  this list. Only works if you also limit chains
983  @param use_CA Limit to CA particles
984  '''
985  ret = []
986  for nxl in range(self.rs.get_number_of_restraints()):
987  this_info = {}
988  xl = IMP.isd.AtomicCrossLinkMSRestraint.get_from(
989  self.rs.get_restraint(nxl))
990  low_dist = 1e6
991  low_pp = None
992  state_contrs = []
993  low_pp_lim = None
994  low_dist_lim = 1e6
995  for contr in range(xl.get_number_of_contributions()):
996  pp = xl.get_contribution(contr)
997  if use_CA:
998  sel = IMP.atom.Selection(
999  IMP.atom.get_residue(IMP.atom.Atom(self.model, pp[0])),
1000  atom_type=IMP.atom.AtomType("CA"))
1001  idx1 = sel.get_selected_particle_indexes()[0]
1002  sel = IMP.atom.Selection(
1003  IMP.atom.get_residue(IMP.atom.Atom(self.model, pp[1])),
1004  atom_type=IMP.atom.AtomType("CA"))
1005  idx2 = sel.get_selected_particle_indexes()[0]
1006  pp = [idx1, idx2]
1007  if limit_to_state is not None:
1008  nstate = IMP.atom.get_state_index(
1009  IMP.atom.Atom(self.model, pp[0]))
1010  if nstate != limit_to_state:
1011  continue
1012  state_contrs.append(contr)
1013  dist = IMP.core.get_distance(IMP.core.XYZ(self.model, pp[0]),
1014  IMP.core.XYZ(self.model, pp[1]))
1015  if limit_to_chains is not None:
1016  c1 = IMP.atom.get_chain_id(
1017  IMP.atom.Atom(self.model, pp[0]))
1018  c2 = IMP.atom.get_chain_id(
1019  IMP.atom.Atom(self.model, pp[1]))
1020  if (c1 in limit_to_chains or c2 in limit_to_chains) and (
1021  c1 not in exclude_chains
1022  and c2 not in exclude_chains):
1023  if dist < low_dist_lim:
1024  low_dist_lim = dist
1025  low_pp_lim = pp
1026  if dist < low_dist:
1027  low_dist = dist
1028  low_pp = pp
1029  if limit_to_state is not None:
1030  this_info["prob"] = xl.evaluate_for_contributions(
1031  state_contrs, None)
1032  else:
1033  this_info["prob"] = xl.unprotected_evaluate(None)
1034  if limit_to_chains is not None:
1035  this_info["low_pp"] = low_pp_lim
1036  else:
1037  this_info["low_pp"] = low_pp
1038 
1039  this_info["low_dist"] = low_dist
1040  if not self.one_psi:
1041  pval = IMP.isd.Scale(self.model, xl.get_psi()).get_scale()
1042  this_info["psi"] = pval
1043  ret.append(this_info)
1044  return ret
1045 
1046  def print_stats(self):
1047  stats = self.get_best_stats()
1048  for nxl, s in enumerate(stats):
1049  print(s["low_dist"])
1050 
1051  def get_output(self):
1052  output = super().get_output()
1053 
1054  # HACK to make it easier to see the few sigmas
1055  # output["AtomicXLRestraint_sigma"] = self.sigma.get_scale()
1056  # if self.one_psi:
1057  # output["AtomicXLRestraint_psi"] = self.psi.get_scale()
1058  #
1059 
1060  # count distances above length
1061  bad_count = 0
1062  stats = self.get_best_stats()
1063  for nxl, s in enumerate(stats):
1064  if s['low_dist'] > 20.0:
1065  bad_count += 1
1066  output["AtomicXLRestraint_%i_%s" % (nxl, "Prob")] = str(s['prob'])
1067  output["AtomicXLRestraint_%i_%s"
1068  % (nxl, "BestDist")] = str(s['low_dist'])
1069  if not self.one_psi:
1070  output["AtomicXLRestraint_%i_%s" % (nxl, "psi")] \
1071  = str(s['psi'])
1072  output["AtomicXLRestraint_NumViol"] = str(bad_count)
1073  return output
1074 
1075 
1076 class CysteineCrossLinkRestraint:
1077  def __init__(self, root_hier, filename, cbeta=False,
1078  betatuple=(0.03, 0.1),
1079  disttuple=(0.0, 25.0, 1000),
1080  omegatuple=(1.0, 1000.0, 50),
1081  sigmatuple=(0.3, 0.3, 1),
1082  betaissampled=True,
1083  sigmaissampled=False,
1084  weightissampled=True,
1085  epsilonissampled=True):
1086  # the file must have residue1 chain1 residue2 chain2 fractionvalue
1087  # epsilonname
1088  # epsilonname is a name for the epsilon particle that must be used
1089  # for that particular residue pair, eg, "Epsilon-Intra-Solvent", or
1090  # "Epsilon-Solvent-Membrane", etc.
1091 
1092  self.m = root_hier.get_model()
1093  self.rs = IMP.RestraintSet(self.m, 'Cysteine_Crosslink')
1094  self.cbeta = cbeta
1095  self.epsilonmaxtrans = 0.01
1096  self.sigmamaxtrans = 0.1
1097  self.betamaxtrans = 0.01
1098  self.weightmaxtrans = 0.1
1099  self.label = "None"
1100  self.outputlevel = "low"
1101  self.betaissampled = betaissampled
1102  self.sigmaissampled = sigmaissampled
1103  self.weightissampled = weightissampled
1104  self.epsilonissampled = epsilonissampled
1105 
1106  betalower = betatuple[0]
1107  betaupper = betatuple[1]
1108  beta = 0.04
1109  sigma = 10.0
1110  betangrid = 30
1111 
1112  # beta
1113  self.beta = IMP.pmi.tools.SetupNuisance(
1114  self.m,
1115  beta,
1116  betalower,
1117  betaupper,
1118  betaissampled).get_particle(
1119  )
1120  # sigma
1121  self.sigma = IMP.pmi.tools.SetupNuisance(
1122  self.m,
1123  sigma,
1124  sigmatuple[0],
1125  sigmatuple[1],
1126  sigmaissampled).get_particle()
1127  # population particle
1128  self.weight = IMP.pmi.tools.SetupWeight(
1129  self.m,
1130  isoptimized=False).get_particle(
1131  )
1132 
1133  # read the file
1134  fl = IMP.pmi.tools.open_file_or_inline_text(filename)
1135  # determine the upperlimit for epsilon
1136  # and also how many epsilon are needed
1137  self.epsilons = {}
1138  data = []
1139  for line in fl:
1140  t = line.split()
1141  if t[0][0] == "#":
1142  continue
1143  if t[5] in self.epsilons:
1144  if 1.0 - float(t[4]) <= self.epsilons[t[5]].get_upper():
1145  self.epsilons[t[5]].set_upper(1.0 - float(t[4]))
1146  else:
1147  self.epsilons[t[5]] = IMP.pmi.tools.SetupNuisance(
1148  self.m, 0.01, 0.01, 1.0 - float(t[4]),
1149  epsilonissampled).get_particle()
1150  up = self.epsilons[t[5]].get_upper()
1151  low = self.epsilons[t[5]].get_lower()
1152  if up < low:
1153  self.epsilons[t[5]].set_upper(low)
1154 
1155  data.append((int(t[0]), t[1], int(t[2]), t[3], float(t[4]), t[5]))
1156 
1157  # create CrossLinkData
1158  if not self.cbeta:
1159  crossdata = IMP.pmi.tools.get_cross_link_data(
1160  "cysteine", "cysteine_CA_FES.txt.standard",
1161  disttuple, omegatuple, sigmatuple, disttuple[1],
1162  disttuple[1], 1)
1163  else:
1164  crossdata = IMP.pmi.tools.get_cross_link_data(
1165  "cysteine", "cysteine_CB_FES.txt.standard",
1166  disttuple, omegatuple, sigmatuple, disttuple[1],
1167  disttuple[1], 1)
1168 
1169  # create grids needed by CysteineCrossLinkData
1170  fmod_grid = IMP.pmi.tools.get_grid(0.0, 1.0, 300, True)
1171  omega2_grid = IMP.pmi.tools.get_log_grid(0.001, 10000.0, 100)
1172  beta_grid = IMP.pmi.tools.get_log_grid(betalower, betaupper, betangrid)
1173 
1174  for d in data:
1175  print("--------------")
1176  print("CysteineCrossLink: attempting to create a restraint "
1177  + str(d))
1178  resid1 = d[0]
1179  chain1 = d[1]
1180  resid2 = d[2]
1181  chain2 = d[3]
1182  fexp = d[4]
1183  epslabel = d[5]
1184 
1185  # CysteineCrossLinkData
1186 
1188  fexp,
1189  fmod_grid,
1190  omega2_grid,
1191  beta_grid)
1192 
1194  self.m,
1195  self.beta,
1196  self.sigma,
1197  self.epsilons[epslabel],
1198  self.weight,
1199  crossdata,
1200  ccldata)
1201 
1202  failed = False
1203  if not self.cbeta:
1204  p1 = None
1205  p2 = None
1206 
1207  p1 = IMP.atom.Selection(root_hier, resolution=1,
1208  molecule=chain1, residue_index=resid1,
1209  copy_index=0)
1210  p1 = p1.get_selected_particles()
1211  if len(p1) > 0:
1212  p1 = p1[0]
1213  else:
1214  failed = True
1215 
1216  p2 = IMP.atom.Selection(root_hier, resolution=1,
1217  molecule=chain2, residue_index=resid2,
1218  copy_index=0)
1219  p2 = p2.get_selected_particles()
1220  if len(p2) > 0:
1221  p2 = p2[0]
1222  else:
1223  failed = True
1224 
1225  else:
1226  # use cbetas
1227  p1 = []
1228  p2 = []
1229  for t in range(-1, 2):
1230  p = IMP.atom.Selection(root_hier, resolution=1,
1231  molecule=chain1, copy_index=0,
1232  residue_index=resid1 + t)
1233  p = p.get_selected_particles()
1234  if len(p) == 1:
1235  p1 += p
1236  else:
1237  failed = True
1238  warnings.warn(
1239  "CysteineCrossLink: missing representation for "
1240  "residue %d of chain %s" % (resid1 + t, chain1),
1242 
1243  p = IMP.atom.Selection(root_hier, resolution=1,
1244  molecule=chain2, copy_index=0,
1245  residue_index=resid2 + t)
1246  p = p.get_selected_particles()
1247  if len(p) == 1:
1248  p2 += p
1249  else:
1250  failed = True
1251  warnings.warn(
1252  "CysteineCrossLink: missing representation for "
1253  "residue %d of chain %s" % (resid2 + t, chain2),
1255 
1256  if not self.cbeta:
1257  if (p1 is not None and p2 is not None):
1258  ccl.add_contribution(p1, p2)
1259  d1 = IMP.core.XYZ(p1)
1260  d2 = IMP.core.XYZ(p2)
1261 
1262  print("Distance_" + str(resid1) + "_" + chain1 + ":" +
1263  str(resid2) + "_" + chain2,
1264  IMP.core.get_distance(d1, d2))
1265 
1266  else:
1267  if (len(p1) == 3 and len(p2) == 3):
1268  p11n = p1[0].get_name()
1269  p12n = p1[1].get_name()
1270  p13n = p1[2].get_name()
1271  p21n = p2[0].get_name()
1272  p22n = p2[1].get_name()
1273  p23n = p2[2].get_name()
1274 
1275  print("CysteineCrossLink: generating CB cysteine "
1276  "cross-link restraint between")
1277  print("CysteineCrossLink: residue %d of chain %s and "
1278  "residue %d of chain %s"
1279  % (resid1, chain1, resid2, chain2))
1280  print("CysteineCrossLink: between particles %s %s %s and "
1281  "%s %s %s" % (p11n, p12n, p13n, p21n, p22n, p23n))
1282 
1283  ccl.add_contribution(p1, p2)
1284 
1285  if not failed:
1286  self.rs.add_restraint(ccl)
1287  ccl.set_name("CysteineCrossLink_" + str(resid1)
1288  + "_" + chain1 + ":" + str(resid2) + "_" + chain2)
1289 
1291  self.weight.get_particle()
1292  ).set_weights_are_optimized(weightissampled)
1293 
1294  def set_label(self, label):
1295  self.label = label
1296 
1297  def add_to_model(self):
1298  IMP.pmi.tools.add_restraint_to_model(self.m, self.rs, add_to_rmf=True)
1299 
1300  def get_particles_to_sample(self):
1301  ps = {}
1302  if self.epsilonissampled:
1303  for eps in self.epsilons.keys():
1304  ps["Nuisances_CysteineCrossLinkRestraint_epsilon_" + eps +
1305  "_" + self.label] = ([self.epsilons[eps]],
1306  self.epsilonmaxtrans)
1307  if self.betaissampled:
1308  ps["Nuisances_CysteineCrossLinkRestraint_beta_" +
1309  self.label] = ([self.beta], self.betamaxtrans)
1310  if self.weightissampled:
1311  ps["Weights_CysteineCrossLinkRestraint_" +
1312  self.label] = ([self.weight], self.weightmaxtrans)
1313  if self.sigmaissampled:
1314  ps["Nuisances_CysteineCrossLinkRestraint_" +
1315  self.label] = ([self.sigma], self.sigmamaxtrans)
1316  return ps
1317 
1318  def set_output_level(self, level="low"):
1319  # this might be "low" or "high"
1320  self.outputlevel = level
1321 
1322  def get_restraint(self):
1323  return self.rs
1324 
1325  def get_sigma(self):
1326  return self.sigma
1327 
1328  def get_output(self):
1329  output = {}
1330  score = self.rs.unprotected_evaluate(None)
1331  output["_TotalScore"] = str(score)
1332  output["CysteineCrossLinkRestraint_Score_" + self.label] = str(score)
1333  output["CysteineCrossLinkRestraint_sigma_" +
1334  self.label] = str(self.sigma.get_scale())
1335  for eps in self.epsilons.keys():
1336  output["CysteineCrossLinkRestraint_epsilon_" + eps + "_" +
1337  self.label] = str(self.epsilons[eps].get_scale())
1338  output["CysteineCrossLinkRestraint_beta_" +
1339  self.label] = str(self.beta.get_scale())
1340  for n in range(self.weight.get_number_of_weights()):
1341  output["CysteineCrossLinkRestraint_weights_" +
1342  str(n) + "_" + self.label] = str(self.weight.get_weight(n))
1343 
1344  if self.outputlevel == "high":
1345  for rst in self.rs.get_restraints():
1346  cclr = IMP.isd.CysteineCrossLinkRestraint.get_from(rst)
1347  output["CysteineCrossLinkRestraint_Total_Frequency_" +
1348  cclr.get_name() +
1349  "_" + self.label] = cclr.get_model_frequency()
1350  output["CysteineCrossLinkRestraint_Standard_Error_" +
1351  cclr.get_name() + "_" +
1352  self.label] = cclr.get_standard_error()
1353  return output
1354 
1355 
1356 class DisulfideCrossLinkRestraint:
1357  def __init__(self, representation_or_hier,
1358  selection_tuple1,
1359  selection_tuple2,
1360  length=6.5,
1361  resolution=1,
1362  slope=0.01,
1363  label="None"):
1364 
1365  self.m = representation_or_hier.get_model()
1366  ps1 = IMP.pmi.tools.select_by_tuple_2(representation_or_hier,
1367  selection_tuple1,
1368  resolution=resolution)
1369  ps2 = IMP.pmi.tools.select_by_tuple_2(representation_or_hier,
1370  selection_tuple2,
1371  resolution=resolution)
1372 
1373  self.rs = IMP.RestraintSet(self.m, 'likelihood')
1374  self.rslin = IMP.RestraintSet(self.m, 'linear_dummy_restraints')
1375 
1376  # dummy linear restraint used for Chimera display
1377  self.linear = IMP.core.Linear(0, 0.0)
1378  self.linear.set_slope(0.0)
1379  dps2 = IMP.core.DistancePairScore(self.linear)
1380 
1381  self.label = label
1382  self.psi_dictionary = {}
1383  self.sigma_dictionary = {}
1384  self.psi_is_sampled = False
1385  self.sigma_is_sampled = False
1386  self.xl = {}
1387 
1388  if len(ps1) > 1 or len(ps1) == 0:
1389  raise ValueError(
1390  "DisulfideBondRestraint: ERROR> first selection pattern "
1391  "selects multiple particles or sero particles")
1392 
1393  if len(ps2) > 1 or len(ps2) == 0:
1394  raise ValueError(
1395  "DisulfideBondRestraint: ERROR> second selection pattern "
1396  "selects multiple particles or sero particles")
1397 
1398  p1 = ps1[0]
1399  p2 = ps2[0]
1400 
1401  sigma = self.create_sigma("SIGMA_DISULFIDE_BOND")
1402  psi = self.create_psi("PSI_DISULFIDE_BOND")
1403 
1404  p1i = p1.get_index()
1405  p2i = p2.get_index()
1406  si = sigma.get_particle().get_index()
1407  psii = psi.get_particle().get_index()
1408 
1409  dr = IMP.isd.CrossLinkMSRestraint(self.m, length, slope)
1410 
1411  dr.add_contribution((p1i, p2i), (si, si), psii)
1412 
1413  if p1i != p2i:
1414  pr = IMP.core.PairRestraint(self.m, dps2, (p1i, p2i))
1415  pr.set_name("DISULFIDE_BOND_"+self.label)
1416  self.rslin.add_restraint(pr)
1417 
1418  lw = IMP.isd.LogWrapper([dr], 1.0)
1419  self.rs.add_restraint(lw)
1420 
1421  self.xl["Particle1"] = p1
1422  self.xl["Particle2"] = p2
1423  self.xl["Sigma"] = sigma
1424  self.xl["Psi"] = psi
1425 
1426  def add_to_model(self):
1427  IMP.pmi.tools.add_restraint_to_model(self.m, self.rs, add_to_rmf=True)
1429  self.m, self.rslin, add_to_rmf=True)
1430 
1431  def get_hierarchies(self):
1432  return self.prot
1433 
1434  def get_restraint_sets(self):
1435  return self.rs
1436 
1437  def get_restraint(self):
1438  return self.rs
1439 
1440  def get_restraint_for_rmf(self):
1441  return self.rslin
1442 
1443  def get_restraints(self):
1444  rlist = []
1445  for r in self.rs.get_restraints():
1446  rlist.append(IMP.core.PairRestraint.get_from(r))
1447  return rlist
1448 
1449  def set_psi_is_sampled(self, is_sampled=True):
1450  self.psi_is_sampled = is_sampled
1451 
1452  def set_sigma_is_sampled(self, is_sampled=True):
1453  self.sigma_is_sampled = is_sampled
1454 
1455  def create_sigma(self, name):
1456  ''' a nuisance on the structural uncertainty '''
1457  if name in self.sigma_dictionary:
1458  return self.sigma_dictionary[name][0]
1459 
1460  sigmainit = 1.0
1461  sigmaminnuis = 0.0000001
1462  sigmamaxnuis = 1000.0
1463  sigmatrans = 0.5
1464  sigma = IMP.pmi.tools.SetupNuisance(
1465  self.m, sigmainit, sigmaminnuis, sigmamaxnuis,
1466  self.sigma_is_sampled).get_particle()
1467  self.sigma_dictionary[name] = (
1468  sigma,
1469  sigmatrans,
1470  self.sigma_is_sampled)
1471 
1472  return sigma
1473 
1474  def create_psi(self, name):
1475  ''' a nuisance on the inconsistency '''
1476  if name in self.psi_dictionary:
1477  return self.psi_dictionary[name][0]
1478 
1479  psiinit = 0.001
1480  psiminnuis = 0.0000001
1481  psimaxnuis = 0.4999999
1482  psitrans = 0.1
1483  psi = IMP.pmi.tools.SetupNuisance(self.m, psiinit,
1484  psiminnuis, psimaxnuis,
1485  self.psi_is_sampled).get_particle()
1486  self.psi_dictionary[name] = (
1487  psi,
1488  psitrans,
1489  self.psi_is_sampled)
1490 
1491  return psi
1492 
1493  def get_output(self):
1494  output = {}
1495  score = self.rs.unprotected_evaluate(None)
1496  output["_TotalScore"] = str(score)
1497  output["DisulfideBondRestraint_Data_Score_" + self.label] = str(score)
1498  output["DisulfideBondRestraint_Linear_Score_" +
1499  self.label] = self.rslin.unprotected_evaluate(None)
1500  return output
1501 
1502  def get_particles_to_sample(self):
1503  raise NotImplementedError(" ")
def set_sigma_is_sampled
Switch on/off the sampling of sigma particles.
Setup cross-link distance restraints from mass spectrometry data.
Definition: crosslinking.py:22
Add weights to a particle.
Definition: Weight.h:29
A restraint for ambiguous cross-linking MS data and multiple state approach.
def get_best_stats
return the probability, best distance, two coords, and possibly the psi for each xl ...
Various classes to hold sets of particles.
def get_particles_to_sample
Get the particles to be sampled by the IMP.pmi.sampler object.
Miscellaneous utilities.
Definition: pmi/tools.py:1
Restrain atom pairs based on a set of crosslinks.
def get_likelihood
Get the unweighted likelihood of the restraint.
Add scale parameter to particle.
Definition: Scale.h:24
The type of an atom.
log
Definition: log.py:1
static bool get_is_setup(const IMP::ParticleAdaptor &p)
Definition: rigid_bodies.h:747
Score distance between two particle centers using a harmonic function.
double get_distance(XYZR a, XYZR b)
Compute the sphere distance between a and b.
Definition: XYZR.h:89
def get_particles_to_sample
Get the particles to be sampled by the IMP.pmi.sampler object.
Uniform distribution with harmonic boundaries.
Definition: UniformPrior.h:22
Object used to hold a set of restraints.
Definition: RestraintSet.h:41
Class for storing model, its restraints, constraints, and particles.
Definition: Model.h:86
Classes to handle different kinds of restraints.
A RestraintSet subclass to track cross-link metadata.
Warning related to handling of structures.
The standard decorator for manipulating molecular structures.
Ints get_index(const ParticlesTemp &particles, const Subset &subset, const Subsets &excluded)
def select_by_tuple_2
New tuple format: molname OR (start,stop,molname,copynum,statenum) Copy and state are optional...
Definition: pmi/tools.py:430
A decorator for a particle representing an atom.
Definition: atom/Atom.h:238
def add_restraint_to_model
Add a PMI restraint to the model.
Definition: pmi/tools.py:84
def get_movers
Get all need data to construct a mover in IMP.pmi.dof class.
def get_particle_pairs
Get a list of tuples containing the particle pairs.
A decorator for a particle with x,y,z coordinates.
Definition: XYZ.h:30
int get_state_index(Hierarchy h)
Walk up the hierarchy to find the current state.
Linear function
Definition: Linear.h:22
def plot_violations
Create CMM files, one for each state, of all cross-links.
Modify a set of continuous variables using a normal distribution.
Definition: NormalMover.h:23
Score a pair of particles based on the distance between their centers.
Classes for writing output files and processing them.
Definition: output.py:1
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...
def create_restraints_for_rmf
create dummy harmonic restraints for each XL but don't add to model Makes it easy to see each contrib...
The general base class for IMP exceptions.
Definition: exception.h:48
Setup cross-link distance restraints at atomic level The "atomic" aspect is that it models the partic...
Residue get_residue(Atom d, bool nothrow=false)
Return the Residue containing this atom.
Calculate the -Log of a list of restraints.
Definition: LogWrapper.h:26
double get_distance(const VectorD< D > &v1, const VectorD< D > &v2)
Compute the distance between two vectors.
Definition: VectorD.h:196
Applies a PairScore to a Pair.
Definition: PairRestraint.h:31
def get_output
Get the output of the restraint to be used by the IMP.pmi.output object.
def get_restraint_for_rmf
get the dummy restraints to be displayed in the rmf file
Functionality for loading, creating, manipulating and scoring atomic structures.
std::string get_chain_id(Hierarchy h)
Walk up the hierarchy to determine the chain id.
def load_nuisances_from_stat_file
Read a stat file and load all the sigmas.
Select hierarchy particles identified by the biological name.
Definition: Selection.h:70
def set_psi_is_sampled
Switch on/off the sampling of psi particles.
Inferential scoring building on methods developed as part of the Inferential Structure Determination ...
A restraint for cysteine cross-linking data.