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