IMP logo
IMP Reference Guide  develop.63b38c487d,2024/12/21
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  output = super().get_output()
429 
430  for xl in self.xl_list:
431 
432  xl_label = xl["ShortLabel"]
433  ln = xl["Restraint"]
434  p0 = xl["Particle1"]
435  p1 = xl["Particle2"]
436  output["CrossLinkingMassSpectrometryRestraint_Score_" +
437  xl_label] = str(-log(ln.unprotected_evaluate(None)))
438 
439  d0 = IMP.core.XYZ(p0)
440  d1 = IMP.core.XYZ(p1)
441  output["CrossLinkingMassSpectrometryRestraint_Distance_" +
442  xl_label] = str(IMP.core.get_distance(d0, d1))
443 
444  for psiname in self.psi_dictionary:
445  output["CrossLinkingMassSpectrometryRestraint_Psi_" +
446  str(psiname) + self._label_suffix] = str(
447  self.psi_dictionary[psiname][0].get_scale())
448 
449  for sigmaname in self.sigma_dictionary:
450  output["CrossLinkingMassSpectrometryRestraint_Sigma_" +
451  str(sigmaname) + self._label_suffix] = str(
452  self.sigma_dictionary[sigmaname][0].get_scale())
453 
454  return output
455 
456  def get_likelihood(self):
457  """Get the unweighted likelihood of the restraint"""
458  likelihood = 1
459  for restraint in self.xl_restraints:
460  likelihood *= restraint.get_probability()
461 
462  return likelihood
463 
464  def get_movers(self):
465  """ Get all need data to construct a mover in IMP.pmi.dof class"""
466  movers = []
467  if self.sigma_is_sampled:
468  for sigmaname in self.sigma_dictionary:
469  mover_name = \
470  "Nuisances_CrossLinkingMassSpectrometryRestraint_Sigma_" \
471  + str(sigmaname) + "_" + self.label
472  particle = self.sigma_dictionary[sigmaname][0]
473  maxstep = (self.sigma_dictionary[sigmaname][1])
475  [particle], IMP.FloatKeys([IMP.FloatKey("nuisance")]),
476  maxstep)
477  mv.set_name(mover_name)
478  movers.append(mv)
479 
480  if self.psi_is_sampled:
481  for psiname in self.psi_dictionary:
482  mover_name = \
483  "Nuisances_CrossLinkingMassSpectrometryRestraint_Psi_" + \
484  str(psiname) + "_" + self.label
485  particle = self.psi_dictionary[psiname][0]
486  maxstep = (self.psi_dictionary[psiname][1])
488  [particle], IMP.FloatKeys([IMP.FloatKey("nuisance")]),
489  maxstep)
490  mv.set_name(mover_name)
491  movers.append(mv)
492 
493  return movers
494 
496  """ Get the particles to be sampled by the IMP.pmi.sampler object """
497  ps = {}
498  if self.sigma_is_sampled:
499  for sigmaname in self.sigma_dictionary:
500  ps["Nuisances_CrossLinkingMassSpectrometryRestraint_Sigma_" +
501  str(sigmaname) + self._label_suffix] =\
502  ([self.sigma_dictionary[sigmaname][0]],
503  self.sigma_dictionary[sigmaname][1])
504 
505  if self.psi_is_sampled:
506  for psiname in self.psi_dictionary:
507  ps["Nuisances_CrossLinkingMassSpectrometryRestraint_Psi_" +
508  str(psiname) + self._label_suffix] = \
509  ([self.psi_dictionary[psiname][0]],
510  self.psi_dictionary[psiname][1])
511 
512  return ps
513 
514 
515 class AtomicCrossLinkMSRestraint(IMP.pmi.restraints.RestraintBase):
516  """Setup cross-link distance restraints at atomic level
517  The "atomic" aspect is that it models the particle uncertainty with
518  a Gaussian. The noise in the data and the structural uncertainty of
519  cross-linked amino-acids is inferred using Bayes' theory of probability
520  @note Wraps an IMP::isd::AtomicCrossLinkMSRestraint
521  """
522 
523  _include_in_rmf = True
524 
525  def __init__(self, root_hier, xldb, atom_type="NZ", length=10.0,
526  slope=0.01, nstates=None, label=None,
527  nuisances_are_optimized=True, sigma_init=5.0,
528  psi_init=0.01, one_psi=True, filelabel=None, weight=1.):
529  """Constructor.
530  Automatically creates one "sigma" per cross-linked residue and one
531  "psis" per pair.
532  Other nuisance options are available.
533  @note Will return an error if the data+extra_sel don't specify
534  two particles per XL pair.
535  @param root_hier The root hierarchy on which you'll do selection
536  @param xldb CrossLinkDataBase object
537  @param atom_type Can either be "NZ" or "CA"
538  @param length The XL linker length
539  @param slope Linear term to add to the restraint function to
540  help when far away
541  @param nstates The number of states to model. Defaults to the
542  number of states in root.
543  @param label The output label for the restraint
544  @param nuisances_are_optimized Whether to optimize nuisances
545  @param sigma_init The initial value for all the sigmas
546  @param psi_init The initial value for all the psis
547  @param one_psi Use a single psi for all restraints (if False,
548  creates one per XL)
549  @param filelabel automatically generated file containing
550  missing/included/excluded
551  cross-links will be labeled using this text
552  @param weight Weight of restraint
553 
554  """
555 
556  # basic params
557  self.root = root_hier
558  rname = "AtomicXLRestraint"
559  super().__init__(
560  self.root.get_model(), name="AtomicXLRestraint", label=label,
561  weight=weight)
562  self.xldb = xldb
563  self.length = length
564  self.sigma_is_sampled = nuisances_are_optimized
565  self.psi_is_sampled = nuisances_are_optimized
566 
567  if atom_type not in ("NZ", "CA"):
568  raise Exception("AtomicXLRestraint: atom_type must be NZ or CA")
569  self.atom_type = atom_type
570  self.nstates = nstates
571  if nstates is None:
572  self.nstates = len(IMP.atom.get_by_type(self.root,
573  IMP.atom.STATE_TYPE))
574  elif nstates != len(IMP.atom.get_by_type(self.root,
575  IMP.atom.STATE_TYPE)):
576  print("Warning: nstates is not the same as the number of states "
577  "in root")
578 
579  self.rs_psi = self._create_restraint_set("psi")
580  self.rs_sig = self._create_restraint_set("sigma")
581  self.rs_lin = self._create_restraint_set("linear")
582 
583  self.psi_dictionary = {}
584  self.sigma_dictionary = {}
585 
586  self.particles = defaultdict(set)
587  self.one_psi = one_psi
588  self.bonded_pairs = []
589  if self.one_psi:
590  print('creating a single psi for all XLs')
591  else:
592  print('creating one psi for each XL')
593 
594  # output logging file
595  if filelabel is not None:
596  indb = open("included." + filelabel + ".xl.db", "w") # noqa: F841
597  exdb = open("excluded." + filelabel + ".xl.db", "w") # noqa: F841
598  midb = open("missing." + filelabel + ".xl.db", "w")
599 
600  # Setup nuisances
601  self._create_sigma('sigma', sigma_init)
602  if one_psi:
603  self._create_psi('psi', psi_init)
604  else:
605  for xlid in self.xldb.xlid_iterator():
606  self._create_psi(xlid, psi_init)
607 
608  # create all the XLs
609  xlrs = []
610  for xlid in self.xldb.xlid_iterator():
611  # create restraint for this data point
612  if one_psi:
613  psip = self.psi_dictionary['psi'][0].get_particle_index()
614  else:
615  psip = self.psi_dictionary[
616  unique_id][0].get_particle_index() # noqa: F821
617  r = IMP.isd.AtomicCrossLinkMSRestraint(self.model,
618  self.length,
619  psip,
620  slope,
621  True)
622  num_contributions = 0
623 
624  # add a contribution for each XL ambiguity option within each state
625  for nstate in self.nstates:
626  for xl in self.xldb[xlid]:
627  r1 = xl[self.xldb.residue1_key]
628  c1 = xl[self.xldb.protein1_key].strip()
629  r2 = xl[self.xldb.residue2_key]
630  c2 = xl[self.xldb.protein2_key].strip()
631 
632  # perform selection. these may contain multiples if
633  # Copies are used
634  ps1 = IMP.atom.Selection(
635  self.root, state_index=nstate,
636  atom_type=IMP.atom.AtomType(self.atom_type),
637  molecule=c1,
638  residue_index=r1).get_selected_particles()
639  ps2 = IMP.atom.Selection(
640  self.root, state_index=nstate,
641  atom_type=IMP.atom.AtomType(self.atom_type),
642  molecule=c2,
643  residue_index=r2).get_selected_particles()
644  if len(ps1) == 0:
645  warnings.warn("AtomicXLRestraint: residue %d of "
646  "chain %s is not there" % (r1, c1),
648  if filelabel is not None:
649  midb.write(str(xl) + "\n")
650  continue
651 
652  if len(ps2) == 0:
653  warnings.warn("AtomicXLRestraint: residue %d of "
654  "chain %s is not there" % (r2, c2),
656  if filelabel is not None:
657  midb.write(str(xl) + "\n")
658  continue
659 
660  # figure out sig1 and sig2 based on num XLs
661  '''
662  num1=num_xls_per_res[str(xl.r1)]
663  num2=num_xls_per_res[str(xl.r2)]
664  if num1<sig_threshold:
665  sig1=self.sig_low
666  else:
667  sig1=self.sig_high
668  if num2<sig_threshold:
669  sig2=self.sig_low
670  else:
671  sig2=self.sig_high
672  '''
673  sig1 = self.sigma_dictionary['sigma'][0]
674  sig2 = self.sigma_dictionary['sigma'][0]
675 
676  # add each copy contribution to restraint
677  for p1, p2 in itertools.product(ps1, ps2):
678  if p1 == p2:
679  continue
680  self.particles[nstate] |= set((p1, p2))
681  r.add_contribution(
682  [p1.get_index(), p2.get_index()],
683  [sig1.get_particle_index(),
684  sig2.get_particle_index()])
685  num_contributions += 1
686 
687  if num_contributions > 0:
688  print('XL:', xlid, 'num contributions:', num_contributions)
689  xlrs.append(r)
690  if len(xlrs) == 0:
691  raise Exception("You didn't create any XL restraints")
692  print('created', len(xlrs), 'XL restraints')
693  rname = self.rs.get_name()
694  self.rs = IMP.isd.LogWrapper(xlrs, self.weight)
695  self.rs.set_name(rname)
696  self.rs.set_weight(self.weight)
697  self.restraint_sets = [self.rs] + self.restraint_sets[1:]
698 
699  def get_hierarchy(self):
700  return self.prot
701 
702  def _create_sigma(self, name, sigmainit):
703  """ This is called internally. Creates a nuisance
704  on the structural uncertainty """
705  if name in self.sigma_dictionary:
706  return self.sigma_dictionary[name][0]
707 
708  sigmaminnuis = 0.0000001
709  sigmamaxnuis = 1000.0
710  sigmamin = 0.01
711  sigmamax = 100.0
712  sigmatrans = 0.5
713  sigma = IMP.pmi.tools.SetupNuisance(
714  self.model, sigmainit, sigmaminnuis, sigmamaxnuis,
715  self.sigma_is_sampled).get_particle()
716  self.sigma_dictionary[name] = (
717  sigma, sigmatrans, self.sigma_is_sampled)
718  self.rs_sig.add_restraint(IMP.isd.UniformPrior(
719  self.model, sigma, 1000000000.0, sigmamax, sigmamin))
720  return sigma
721 
722  def _create_psi(self, name, psiinit):
723  """ This is called internally. Creates a nuisance
724  on the data uncertainty """
725  if name in self.psi_dictionary:
726  return self.psi_dictionary[name][0]
727 
728  psiminnuis = 0.0000001
729  psimaxnuis = 0.4999999
730  psimin = 0.01
731  psimax = 0.49
732  psitrans = 0.1
733  psi = IMP.pmi.tools.SetupNuisance(self.model,
734  psiinit,
735  psiminnuis,
736  psimaxnuis,
737  self.psi_is_sampled).get_particle()
738  self.psi_dictionary[name] = (
739  psi,
740  psitrans,
741  self.psi_is_sampled)
742 
743  self.rs_psi.add_restraint(
745  self.model,
746  psi,
747  1000000000.0,
748  psimax,
749  psimin))
750 
751  self.rs_psi.add_restraint(IMP.isd.JeffreysRestraint(self.model, psi))
752  return psi
753 
755  """ create dummy harmonic restraints for each XL but don't add to model
756  Makes it easy to see each contribution to each XL in RMF
757  """
758  class MyGetRestraint:
759  def __init__(self, rs):
760  self.rs = rs
761 
762  def get_restraint_for_rmf(self):
763  return self.rs
764 
765  dummy_model = IMP.Model()
766  hps = IMP.core.HarmonicDistancePairScore(self.length, 1.0)
767  dummy_rs = []
768  for nxl in range(self.get_number_of_restraints()):
769  xl = IMP.isd.AtomicCrossLinkMSRestraint.get_from(
770  self.rs.get_restraint(nxl))
771  rs = IMP.RestraintSet(dummy_model, 'atomic_xl_'+str(nxl))
772  for ncontr in range(xl.get_number_of_contributions()):
773  ps = xl.get_contribution(ncontr)
775  hps, [self.model.get_particle(p) for p in ps],
776  'xl%i_contr%i' % (nxl, ncontr))
777  rs.add_restraint(dr)
778  dummy_rs.append(MyGetRestraint(rs))
779  return dummy_rs
780 
782  """ Get the particles to be sampled by the IMP.pmi.sampler object """
783  ps = {}
784  if self.sigma_is_sampled:
785  for sigmaname in self.sigma_dictionary:
786  ps["Nuisances_AtomicCrossLinkingMSRestraint_Sigma_" +
787  str(sigmaname) + self._label_suffix] = \
788  ([self.sigma_dictionary[sigmaname][0]],
789  self.sigma_dictionary[sigmaname][1])
790  if self.psi_is_sampled:
791  for psiname in self.psi_dictionary:
792  ps["Nuisances_CrossLinkingMassSpectrometryRestraint_Psi_" +
793  str(psiname) + self._label_suffix] = \
794  ([self.psi_dictionary[psiname][0]],
795  self.psi_dictionary[psiname][1])
796  return ps
797 
798  def get_bonded_pairs(self):
799  return self.bonded_pairs
800 
801  def get_number_of_restraints(self):
802  return self.rs.get_number_of_restraints()
803 
804  def __repr__(self):
805  return 'XL restraint with ' + \
806  str(len(self.rs.get_restraint(0).get_number_of_restraints())) \
807  + ' data points'
808 
809  def load_nuisances_from_stat_file(self, in_fn, nframe):
810  """Read a stat file and load all the sigmas.
811  This is potentially quite stupid.
812  It's also a hack since the sigmas should be stored in the RMF file.
813  Also, requires one sigma and one psi for ALL XLs.
814  """
815  import subprocess
816  sig_val = float(subprocess.check_output(
817  ["process_output.py", "-f", in_fn, "-s",
818  "AtomicXLRestraint_sigma"]).split('\n>')[1+nframe])
819  psi_val = float(subprocess.check_output(
820  ["process_output.py", "-f", in_fn, "-s",
821  "AtomicXLRestraint_psi"]).split('\n>')[1+nframe])
822  for nxl in range(self.rs.get_number_of_restraints()):
823  xl = IMP.isd.AtomicCrossLinkMSRestraint.get_from(
824  self.rs.get_restraint(nxl))
825  psip = xl.get_psi()
826  IMP.isd.Scale(self.model, psip).set_scale(psi_val)
827  for contr in range(xl.get_number_of_contributions()):
828  sig1, sig2 = xl.get_contribution_sigmas(contr)
829  IMP.isd.Scale(self.model, sig1).set_scale(sig_val)
830 
831  print('loaded nuisances from file')
832 
833  def plot_violations(self, out_prefix, max_prob_for_violation=0.1,
834  min_dist_for_violation=1e9, coarsen=False,
835  limit_to_chains=None, exclude_chains=''):
836  """Create CMM files, one for each state, of all cross-links.
837  will draw in GREEN if non-violated in all states (or if only one state)
838  will draw in PURPLE if non-violated only in a subset of states
839  (draws nothing elsewhere)
840  will draw in RED in ALL states if all violated
841  (if only one state, you'll only see green and red)
842 
843  @param out_prefix Output xlink files prefix
844  @param max_prob_for_violation It's a violation if the probability
845  is below this
846  @param min_dist_for_violation It's a violation if the min dist
847  is above this
848  @param coarsen Use CA positions
849  @param limit_to_chains Try to visualize just these chains
850  @param exclude_chains Try to NOT visualize these chains
851  """
852  print('going to calculate violations and plot CMM files')
853  all_stats = self.get_best_stats()
854  all_dists = [s["low_dist"] for s in all_stats]
855 
856  # prepare one output file per state
857  out_fns = []
858  out_nvs = []
859  state_info = []
860  cmds = defaultdict(set)
861  for nstate in range(self.nstates):
862  outf = open(out_prefix+str(nstate)+'.cmm', 'w')
863  outf.write('<marker_set name="xlinks_state%i"> \n' % nstate)
864  out_fns.append(outf)
865  out_nvs.append(0)
866  print('will limit to', limit_to_chains)
867  print('will exclude', exclude_chains)
868  state_info.append(self.get_best_stats(nstate,
869  limit_to_chains,
870  exclude_chains))
871 
872  for nxl in range(self.rs.get_number_of_restraints()):
873  # for this XL, check which states passed
874  npass = []
875  nviol = []
876  for nstate in range(self.nstates):
877  prob = state_info[nstate][nxl]["prob"]
878  low_dist = state_info[nstate][nxl]["low_dist"]
879  if (prob < max_prob_for_violation or
880  low_dist > min_dist_for_violation):
881  nviol.append(nstate)
882  else:
883  npass.append(nstate)
884 
885  # special coloring when all pass or all fail
886  all_pass = False
887  all_viol = False
888  if len(npass) == self.nstates:
889  all_pass = True
890  elif len(nviol) == self.nstates:
891  all_viol = True
892 
893  # finally, color based on above info
894  print(nxl, 'state dists:',
895  [state_info[nstate][nxl]["low_dist"]
896  for nstate in range(self.nstates)],
897  'viol states:', nviol, 'all viol?', all_viol)
898  for nstate in range(self.nstates):
899  if all_pass:
900  r = 0.365
901  g = 0.933
902  b = 0.365
903  continue
904  elif all_viol:
905  r = 0.980
906  g = 0.302
907  b = 0.247
908  continue
909  else:
910  if nstate in nviol:
911  continue
912  else:
913  r = 0.365
914  g = 0.933
915  b = 0.365
916  # now only showing if UNIQUELY PASSING in this state
917  pp = state_info[nstate][nxl]["low_pp"]
918  c1 = IMP.core.XYZ(self.model, pp[0]).get_coordinates()
919  c2 = IMP.core.XYZ(self.model, pp[1]).get_coordinates()
920 
922  IMP.atom.Atom(self.model, pp[0])).get_index()
923  ch1 = IMP.atom.get_chain_id(IMP.atom.Atom(self.model, pp[0]))
925  IMP.atom.Atom(self.model, pp[0])).get_index()
926  ch2 = IMP.atom.get_chain_id(IMP.atom.Atom(self.model, pp[0]))
927 
928  cmds[nstate].add((ch1, r1))
929  cmds[nstate].add((ch2, r2))
930 
931  outf = out_fns[nstate]
932  nv = out_nvs[nstate]
933  outf.write('<marker id= "%d" x="%.3f" y="%.3f" z="%.3f" '
934  'radius="0.8" r="%.2f" g="%.2f" b="%.2f"/> \n'
935  % (nv, c1[0], c1[1], c1[2], r, g, b))
936  outf.write('<marker id= "%d" x="%.3f" y="%.3f" z="%.3f" '
937  'radius="0.8" r="%.2f" g="%.2f" b="%.2f"/> \n'
938  % (nv+1, c2[0], c2[1], c2[2], r, g, b))
939  outf.write('<link id1= "%d" id2="%d" radius="0.8" '
940  'r="%.2f" g="%.2f" b="%.2f"/> \n'
941  % (nv, nv+1, r, g, b))
942  out_nvs[nstate] += 2
943 
944  for nstate in range(self.nstates):
945  out_fns[nstate].write('</marker_set>\n')
946  out_fns[nstate].close()
947  cmd = ''
948  for ch, r in cmds[nstate]:
949  cmd += '#%i:%i.%s ' % (nstate, r, ch)
950  print(cmd)
951 
952  return all_dists
953 
954  def _get_contribution_info(self, xl, ncontr, use_CA=False):
955  """Return the particles at that contribution. If requested will
956  return CA's instead"""
957  idx1 = xl.get_contribution(ncontr)[0]
958  idx2 = xl.get_contribution(ncontr)[1]
959  if use_CA:
960  sel = IMP.atom.Selection(
961  IMP.atom.get_residue(IMP.atom.Atom(self.model, idx1)),
962  atom_type=IMP.atom.AtomType("CA"))
963  idx1 = sel.get_selected_particle_indexes()[0]
964  sel = IMP.atom.Selection(
965  IMP.atom.get_residue(IMP.atom.Atom(self.model, idx2)),
966  atom_type=IMP.atom.AtomType("CA"))
967  idx2 = sel.get_selected_particle_indexes()[0]
969  IMP.core.XYZ(self.model, idx1).get_coordinates(),
970  IMP.core.XYZ(self.model, idx2).get_coordinates())
971  return idx1, idx2, dist
972 
973  def get_best_stats(self, limit_to_state=None, limit_to_chains=None,
974  exclude_chains='', use_CA=False):
975  ''' return the probability, best distance, two coords, and possibly
976  the psi for each xl
977  @param limit_to_state Only examine contributions from one state
978  @param limit_to_chains Returns the particles for certain "easy
979  to visualize" chains
980  @param exclude_chains Even if you limit, don't let one end be in
981  this list. Only works if you also limit chains
982  @param use_CA Limit to CA particles
983  '''
984  ret = []
985  for nxl in range(self.rs.get_number_of_restraints()):
986  this_info = {}
987  xl = IMP.isd.AtomicCrossLinkMSRestraint.get_from(
988  self.rs.get_restraint(nxl))
989  low_dist = 1e6
990  low_pp = None
991  state_contrs = []
992  low_pp_lim = None
993  low_dist_lim = 1e6
994  for contr in range(xl.get_number_of_contributions()):
995  pp = xl.get_contribution(contr)
996  if use_CA:
997  sel = IMP.atom.Selection(
998  IMP.atom.get_residue(IMP.atom.Atom(self.model, pp[0])),
999  atom_type=IMP.atom.AtomType("CA"))
1000  idx1 = sel.get_selected_particle_indexes()[0]
1001  sel = IMP.atom.Selection(
1002  IMP.atom.get_residue(IMP.atom.Atom(self.model, pp[1])),
1003  atom_type=IMP.atom.AtomType("CA"))
1004  idx2 = sel.get_selected_particle_indexes()[0]
1005  pp = [idx1, idx2]
1006  if limit_to_state is not None:
1007  nstate = IMP.atom.get_state_index(
1008  IMP.atom.Atom(self.model, pp[0]))
1009  if nstate != limit_to_state:
1010  continue
1011  state_contrs.append(contr)
1012  dist = IMP.core.get_distance(IMP.core.XYZ(self.model, pp[0]),
1013  IMP.core.XYZ(self.model, pp[1]))
1014  if limit_to_chains is not None:
1015  c1 = IMP.atom.get_chain_id(
1016  IMP.atom.Atom(self.model, pp[0]))
1017  c2 = IMP.atom.get_chain_id(
1018  IMP.atom.Atom(self.model, pp[1]))
1019  if (c1 in limit_to_chains or c2 in limit_to_chains) and (
1020  c1 not in exclude_chains
1021  and c2 not in exclude_chains):
1022  if dist < low_dist_lim:
1023  low_dist_lim = dist
1024  low_pp_lim = pp
1025  if dist < low_dist:
1026  low_dist = dist
1027  low_pp = pp
1028  if limit_to_state is not None:
1029  this_info["prob"] = xl.evaluate_for_contributions(
1030  state_contrs, None)
1031  else:
1032  this_info["prob"] = xl.unprotected_evaluate(None)
1033  if limit_to_chains is not None:
1034  this_info["low_pp"] = low_pp_lim
1035  else:
1036  this_info["low_pp"] = low_pp
1037 
1038  this_info["low_dist"] = low_dist
1039  if not self.one_psi:
1040  pval = IMP.isd.Scale(self.model, xl.get_psi()).get_scale()
1041  this_info["psi"] = pval
1042  ret.append(this_info)
1043  return ret
1044 
1045  def print_stats(self):
1046  stats = self.get_best_stats()
1047  for nxl, s in enumerate(stats):
1048  print(s["low_dist"])
1049 
1050  def get_output(self):
1051  output = super().get_output()
1052 
1053  # HACK to make it easier to see the few sigmas
1054  # output["AtomicXLRestraint_sigma"] = self.sigma.get_scale()
1055  # if self.one_psi:
1056  # output["AtomicXLRestraint_psi"] = self.psi.get_scale()
1057  #
1058 
1059  # count distances above length
1060  bad_count = 0
1061  stats = self.get_best_stats()
1062  for nxl, s in enumerate(stats):
1063  if s['low_dist'] > 20.0:
1064  bad_count += 1
1065  output["AtomicXLRestraint_%i_%s" % (nxl, "Prob")] = str(s['prob'])
1066  output["AtomicXLRestraint_%i_%s"
1067  % (nxl, "BestDist")] = str(s['low_dist'])
1068  if not self.one_psi:
1069  output["AtomicXLRestraint_%i_%s" % (nxl, "psi")] \
1070  = str(s['psi'])
1071  output["AtomicXLRestraint_NumViol"] = str(bad_count)
1072  return output
1073 
1074 
1075 class CysteineCrossLinkRestraint:
1076  def __init__(self, root_hier, filename, cbeta=False,
1077  betatuple=(0.03, 0.1),
1078  disttuple=(0.0, 25.0, 1000),
1079  omegatuple=(1.0, 1000.0, 50),
1080  sigmatuple=(0.3, 0.3, 1),
1081  betaissampled=True,
1082  sigmaissampled=False,
1083  weightissampled=True,
1084  epsilonissampled=True):
1085  # the file must have residue1 chain1 residue2 chain2 fractionvalue
1086  # epsilonname
1087  # epsilonname is a name for the epsilon particle that must be used
1088  # for that particular residue pair, eg, "Epsilon-Intra-Solvent", or
1089  # "Epsilon-Solvent-Membrane", etc.
1090 
1091  self.m = root_hier.get_model()
1092  self.rs = IMP.RestraintSet(self.m, 'Cysteine_Crosslink')
1093  self.cbeta = cbeta
1094  self.epsilonmaxtrans = 0.01
1095  self.sigmamaxtrans = 0.1
1096  self.betamaxtrans = 0.01
1097  self.weightmaxtrans = 0.1
1098  self.label = "None"
1099  self.outputlevel = "low"
1100  self.betaissampled = betaissampled
1101  self.sigmaissampled = sigmaissampled
1102  self.weightissampled = weightissampled
1103  self.epsilonissampled = epsilonissampled
1104 
1105  betalower = betatuple[0]
1106  betaupper = betatuple[1]
1107  beta = 0.04
1108  sigma = 10.0
1109  betangrid = 30
1110 
1111  # beta
1112  self.beta = IMP.pmi.tools.SetupNuisance(
1113  self.m,
1114  beta,
1115  betalower,
1116  betaupper,
1117  betaissampled).get_particle(
1118  )
1119  # sigma
1120  self.sigma = IMP.pmi.tools.SetupNuisance(
1121  self.m,
1122  sigma,
1123  sigmatuple[0],
1124  sigmatuple[1],
1125  sigmaissampled).get_particle()
1126  # population particle
1127  self.weight = IMP.pmi.tools.SetupWeight(
1128  self.m,
1129  isoptimized=False).get_particle(
1130  )
1131 
1132  # read the file
1133  fl = IMP.pmi.tools.open_file_or_inline_text(filename)
1134  # determine the upperlimit for epsilon
1135  # and also how many epsilon are needed
1136  self.epsilons = {}
1137  data = []
1138  for line in fl:
1139  t = line.split()
1140  if t[0][0] == "#":
1141  continue
1142  if t[5] in self.epsilons:
1143  if 1.0 - float(t[4]) <= self.epsilons[t[5]].get_upper():
1144  self.epsilons[t[5]].set_upper(1.0 - float(t[4]))
1145  else:
1146  self.epsilons[t[5]] = IMP.pmi.tools.SetupNuisance(
1147  self.m, 0.01, 0.01, 1.0 - float(t[4]),
1148  epsilonissampled).get_particle()
1149  up = self.epsilons[t[5]].get_upper()
1150  low = self.epsilons[t[5]].get_lower()
1151  if up < low:
1152  self.epsilons[t[5]].set_upper(low)
1153 
1154  data.append((int(t[0]), t[1], int(t[2]), t[3], float(t[4]), t[5]))
1155 
1156  # create CrossLinkData
1157  if not self.cbeta:
1158  crossdata = IMP.pmi.tools.get_cross_link_data(
1159  "cysteine", "cysteine_CA_FES.txt.standard",
1160  disttuple, omegatuple, sigmatuple, disttuple[1],
1161  disttuple[1], 1)
1162  else:
1163  crossdata = IMP.pmi.tools.get_cross_link_data(
1164  "cysteine", "cysteine_CB_FES.txt.standard",
1165  disttuple, omegatuple, sigmatuple, disttuple[1],
1166  disttuple[1], 1)
1167 
1168  # create grids needed by CysteineCrossLinkData
1169  fmod_grid = IMP.pmi.tools.get_grid(0.0, 1.0, 300, True)
1170  omega2_grid = IMP.pmi.tools.get_log_grid(0.001, 10000.0, 100)
1171  beta_grid = IMP.pmi.tools.get_log_grid(betalower, betaupper, betangrid)
1172 
1173  for d in data:
1174  print("--------------")
1175  print("CysteineCrossLink: attempting to create a restraint "
1176  + str(d))
1177  resid1 = d[0]
1178  chain1 = d[1]
1179  resid2 = d[2]
1180  chain2 = d[3]
1181  fexp = d[4]
1182  epslabel = d[5]
1183 
1184  # CysteineCrossLinkData
1185 
1187  fexp,
1188  fmod_grid,
1189  omega2_grid,
1190  beta_grid)
1191 
1193  self.m,
1194  self.beta,
1195  self.sigma,
1196  self.epsilons[epslabel],
1197  self.weight,
1198  crossdata,
1199  ccldata)
1200 
1201  failed = False
1202  if not self.cbeta:
1203  p1 = None
1204  p2 = None
1205 
1206  p1 = IMP.atom.Selection(root_hier, resolution=1,
1207  molecule=chain1, residue_index=resid1,
1208  copy_index=0)
1209  p1 = p1.get_selected_particles()
1210  if len(p1) > 0:
1211  p1 = p1[0]
1212  else:
1213  failed = True
1214 
1215  p2 = IMP.atom.Selection(root_hier, resolution=1,
1216  molecule=chain2, residue_index=resid2,
1217  copy_index=0)
1218  p2 = p2.get_selected_particles()
1219  if len(p2) > 0:
1220  p2 = p2[0]
1221  else:
1222  failed = True
1223 
1224  else:
1225  # use cbetas
1226  p1 = []
1227  p2 = []
1228  for t in range(-1, 2):
1229  p = IMP.atom.Selection(root_hier, resolution=1,
1230  molecule=chain1, copy_index=0,
1231  residue_index=resid1 + t)
1232  p = p.get_selected_particles()
1233  if len(p) == 1:
1234  p1 += p
1235  else:
1236  failed = True
1237  warnings.warn(
1238  "CysteineCrossLink: missing representation for "
1239  "residue %d of chain %s" % (resid1 + t, chain1),
1241 
1242  p = IMP.atom.Selection(root_hier, resolution=1,
1243  molecule=chain2, copy_index=0,
1244  residue_index=resid2 + t)
1245  p = p.get_selected_particles()
1246  if len(p) == 1:
1247  p2 += p
1248  else:
1249  failed = True
1250  warnings.warn(
1251  "CysteineCrossLink: missing representation for "
1252  "residue %d of chain %s" % (resid2 + t, chain2),
1254 
1255  if not self.cbeta:
1256  if (p1 is not None and p2 is not None):
1257  ccl.add_contribution(p1, p2)
1258  d1 = IMP.core.XYZ(p1)
1259  d2 = IMP.core.XYZ(p2)
1260 
1261  print("Distance_" + str(resid1) + "_" + chain1 + ":" +
1262  str(resid2) + "_" + chain2,
1263  IMP.core.get_distance(d1, d2))
1264 
1265  else:
1266  if (len(p1) == 3 and len(p2) == 3):
1267  p11n = p1[0].get_name()
1268  p12n = p1[1].get_name()
1269  p13n = p1[2].get_name()
1270  p21n = p2[0].get_name()
1271  p22n = p2[1].get_name()
1272  p23n = p2[2].get_name()
1273 
1274  print("CysteineCrossLink: generating CB cysteine "
1275  "cross-link restraint between")
1276  print("CysteineCrossLink: residue %d of chain %s and "
1277  "residue %d of chain %s"
1278  % (resid1, chain1, resid2, chain2))
1279  print("CysteineCrossLink: between particles %s %s %s and "
1280  "%s %s %s" % (p11n, p12n, p13n, p21n, p22n, p23n))
1281 
1282  ccl.add_contribution(p1, p2)
1283 
1284  if not failed:
1285  self.rs.add_restraint(ccl)
1286  ccl.set_name("CysteineCrossLink_" + str(resid1)
1287  + "_" + chain1 + ":" + str(resid2) + "_" + chain2)
1288 
1290  self.weight.get_particle()
1291  ).set_weights_are_optimized(weightissampled)
1292 
1293  def set_label(self, label):
1294  self.label = label
1295 
1296  def add_to_model(self):
1297  IMP.pmi.tools.add_restraint_to_model(self.m, self.rs, add_to_rmf=True)
1298 
1299  def get_particles_to_sample(self):
1300  ps = {}
1301  if self.epsilonissampled:
1302  for eps in self.epsilons.keys():
1303  ps["Nuisances_CysteineCrossLinkRestraint_epsilon_" + eps +
1304  "_" + self.label] = ([self.epsilons[eps]],
1305  self.epsilonmaxtrans)
1306  if self.betaissampled:
1307  ps["Nuisances_CysteineCrossLinkRestraint_beta_" +
1308  self.label] = ([self.beta], self.betamaxtrans)
1309  if self.weightissampled:
1310  ps["Weights_CysteineCrossLinkRestraint_" +
1311  self.label] = ([self.weight], self.weightmaxtrans)
1312  if self.sigmaissampled:
1313  ps["Nuisances_CysteineCrossLinkRestraint_" +
1314  self.label] = ([self.sigma], self.sigmamaxtrans)
1315  return ps
1316 
1317  def set_output_level(self, level="low"):
1318  # this might be "low" or "high"
1319  self.outputlevel = level
1320 
1321  def get_restraint(self):
1322  return self.rs
1323 
1324  def get_sigma(self):
1325  return self.sigma
1326 
1327  def get_output(self):
1328  output = {}
1329  score = self.rs.unprotected_evaluate(None)
1330  output["_TotalScore"] = str(score)
1331  output["CysteineCrossLinkRestraint_Score_" + self.label] = str(score)
1332  output["CysteineCrossLinkRestraint_sigma_" +
1333  self.label] = str(self.sigma.get_scale())
1334  for eps in self.epsilons.keys():
1335  output["CysteineCrossLinkRestraint_epsilon_" + eps + "_" +
1336  self.label] = str(self.epsilons[eps].get_scale())
1337  output["CysteineCrossLinkRestraint_beta_" +
1338  self.label] = str(self.beta.get_scale())
1339  for n in range(self.weight.get_number_of_weights()):
1340  output["CysteineCrossLinkRestraint_weights_" +
1341  str(n) + "_" + self.label] = str(self.weight.get_weight(n))
1342 
1343  if self.outputlevel == "high":
1344  for rst in self.rs.get_restraints():
1345  cclr = IMP.isd.CysteineCrossLinkRestraint.get_from(rst)
1346  output["CysteineCrossLinkRestraint_Total_Frequency_" +
1347  cclr.get_name() +
1348  "_" + self.label] = cclr.get_model_frequency()
1349  output["CysteineCrossLinkRestraint_Standard_Error_" +
1350  cclr.get_name() + "_" +
1351  self.label] = cclr.get_standard_error()
1352  return output
1353 
1354 
1355 class DisulfideCrossLinkRestraint:
1356  def __init__(self, representation_or_hier,
1357  selection_tuple1,
1358  selection_tuple2,
1359  length=6.5,
1360  resolution=1,
1361  slope=0.01,
1362  label="None"):
1363 
1364  self.m = representation_or_hier.get_model()
1365  ps1 = IMP.pmi.tools.select_by_tuple_2(representation_or_hier,
1366  selection_tuple1,
1367  resolution=resolution)
1368  ps2 = IMP.pmi.tools.select_by_tuple_2(representation_or_hier,
1369  selection_tuple2,
1370  resolution=resolution)
1371 
1372  self.rs = IMP.RestraintSet(self.m, 'likelihood')
1373  self.rslin = IMP.RestraintSet(self.m, 'linear_dummy_restraints')
1374 
1375  # dummy linear restraint used for Chimera display
1376  self.linear = IMP.core.Linear(0, 0.0)
1377  self.linear.set_slope(0.0)
1378  dps2 = IMP.core.DistancePairScore(self.linear)
1379 
1380  self.label = label
1381  self.psi_dictionary = {}
1382  self.sigma_dictionary = {}
1383  self.psi_is_sampled = False
1384  self.sigma_is_sampled = False
1385  self.xl = {}
1386 
1387  if len(ps1) > 1 or len(ps1) == 0:
1388  raise ValueError(
1389  "DisulfideBondRestraint: ERROR> first selection pattern "
1390  "selects multiple particles or sero particles")
1391 
1392  if len(ps2) > 1 or len(ps2) == 0:
1393  raise ValueError(
1394  "DisulfideBondRestraint: ERROR> second selection pattern "
1395  "selects multiple particles or sero particles")
1396 
1397  p1 = ps1[0]
1398  p2 = ps2[0]
1399 
1400  sigma = self.create_sigma("SIGMA_DISULFIDE_BOND")
1401  psi = self.create_psi("PSI_DISULFIDE_BOND")
1402 
1403  p1i = p1.get_index()
1404  p2i = p2.get_index()
1405  si = sigma.get_particle().get_index()
1406  psii = psi.get_particle().get_index()
1407 
1408  dr = IMP.isd.CrossLinkMSRestraint(self.m, length, slope)
1409 
1410  dr.add_contribution((p1i, p2i), (si, si), psii)
1411 
1412  if p1i != p2i:
1413  pr = IMP.core.PairRestraint(self.m, dps2, (p1i, p2i))
1414  pr.set_name("DISULFIDE_BOND_"+self.label)
1415  self.rslin.add_restraint(pr)
1416 
1417  lw = IMP.isd.LogWrapper([dr], 1.0)
1418  self.rs.add_restraint(lw)
1419 
1420  self.xl["Particle1"] = p1
1421  self.xl["Particle2"] = p2
1422  self.xl["Sigma"] = sigma
1423  self.xl["Psi"] = psi
1424 
1425  def add_to_model(self):
1426  IMP.pmi.tools.add_restraint_to_model(self.m, self.rs, add_to_rmf=True)
1428  self.m, self.rslin, add_to_rmf=True)
1429 
1430  def get_hierarchies(self):
1431  return self.prot
1432 
1433  def get_restraint_sets(self):
1434  return self.rs
1435 
1436  def get_restraint(self):
1437  return self.rs
1438 
1439  def get_restraint_for_rmf(self):
1440  return self.rslin
1441 
1442  def get_restraints(self):
1443  rlist = []
1444  for r in self.rs.get_restraints():
1445  rlist.append(IMP.core.PairRestraint.get_from(r))
1446  return rlist
1447 
1448  def set_psi_is_sampled(self, is_sampled=True):
1449  self.psi_is_sampled = is_sampled
1450 
1451  def set_sigma_is_sampled(self, is_sampled=True):
1452  self.sigma_is_sampled = is_sampled
1453 
1454  def create_sigma(self, name):
1455  ''' a nuisance on the structural uncertainty '''
1456  if name in self.sigma_dictionary:
1457  return self.sigma_dictionary[name][0]
1458 
1459  sigmainit = 1.0
1460  sigmaminnuis = 0.0000001
1461  sigmamaxnuis = 1000.0
1462  sigmatrans = 0.5
1463  sigma = IMP.pmi.tools.SetupNuisance(
1464  self.m, sigmainit, sigmaminnuis, sigmamaxnuis,
1465  self.sigma_is_sampled).get_particle()
1466  self.sigma_dictionary[name] = (
1467  sigma,
1468  sigmatrans,
1469  self.sigma_is_sampled)
1470 
1471  return sigma
1472 
1473  def create_psi(self, name):
1474  ''' a nuisance on the inconsistency '''
1475  if name in self.psi_dictionary:
1476  return self.psi_dictionary[name][0]
1477 
1478  psiinit = 0.001
1479  psiminnuis = 0.0000001
1480  psimaxnuis = 0.4999999
1481  psitrans = 0.1
1482  psi = IMP.pmi.tools.SetupNuisance(self.m, psiinit,
1483  psiminnuis, psimaxnuis,
1484  self.psi_is_sampled).get_particle()
1485  self.psi_dictionary[name] = (
1486  psi,
1487  psitrans,
1488  self.psi_is_sampled)
1489 
1490  return psi
1491 
1492  def get_output(self):
1493  output = {}
1494  score = self.rs.unprotected_evaluate(None)
1495  output["_TotalScore"] = str(score)
1496  output["DisulfideBondRestraint_Data_Score_" + self.label] = str(score)
1497  output["DisulfideBondRestraint_Linear_Score_" +
1498  self.label] = self.rslin.unprotected_evaluate(None)
1499  return output
1500 
1501  def get_particles_to_sample(self):
1502  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: 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:749
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: 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: 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.