IMP logo
IMP Reference Guide  2.19.0
The Integrative Modeling Platform
mmcif/restraint.py
1 """@namespace IMP.mmcif.restraint
2  @brief Map IMP restraints to mmCIF categories
3 """
4 
6 import ihm.restraint
7 
8 
9 def _get_input_pis(r):
10  """Yield all input ParticleIndexes for a given Restraint"""
11  for inp in r.get_inputs():
12  try:
13  p = IMP.Particle.get_from(inp)
14  yield p.get_index()
15  except ValueError:
16  pass
17 
18 
19 def _get_by_residue(m, p):
20  """Determine whether the given particle represents a specific residue
21  or a more coarse-grained object."""
22  return (IMP.atom.Residue.get_is_setup(m, p)
24 
25 
26 def _get_scale(m, p):
27  """Get the numerical value of the Scale particle"""
28  if not IMP.isd.Scale.get_is_setup(m, p):
29  raise ValueError("not scale particle")
30  return IMP.isd.Scale(m, p).get_scale()
31 
32 
33 class _AsymMapper(object):
34  """Map ParticleIndexes to ihm.AsymUnit"""
35  def __init__(self, m, system):
36  self.m = m
37  self.components = system.components
38  self._seen_ranges = {}
39 
40  def __getitem__(self, pi):
41  m = self.m
42  # Walk up the hierarchy until we find the Chain,
43  # then map that to AsymUnit
45  h = IMP.atom.Hierarchy(m, pi)
46  while h:
48  return self.components[IMP.atom.Chain(h)].asym_unit
49  h = h.get_parent()
50  raise KeyError("Could not find top-level Chain for "
51  + m.get_particle_name(pi))
52 
53  def get_feature(self, ps):
54  """Get an ihm.restraint.Feature that covers the given particles"""
55  # todo: handle things other than residues
56  m = self.m
57  rngs = []
58  for p in ps:
59  asym = self[p]
60  # todo: handle overlapping ranges
62  rind = IMP.atom.Residue(m, p).get_index()
63  rng = asym(rind, rind)
65  # PMI Fragments always contain contiguous residues
66  rinds = IMP.atom.Fragment(m, p).get_residue_indexes()
67  rng = asym(rinds[0], rinds[-1])
68  else:
69  raise ValueError("Unsupported particle type %s" % str(p))
70  # Join contiguous ranges
71  if len(rngs) > 0 and rngs[-1].asym == asym \
72  and rngs[-1].seq_id_range[1] == rng.seq_id_range[0] - 1:
73  rngs[-1].seq_id_range = (rngs[-1].seq_id_range[0],
74  rng.seq_id_range[1])
75  else:
76  rngs.append(rng)
77  # If an identical feature already exists, return that
78  # todo: python-ihm should handle this automatically for us
79  hrngs = tuple(rngs)
80  if hrngs in self._seen_ranges:
81  return self._seen_ranges[hrngs]
82  else:
83  feat = ihm.restraint.ResidueFeature(rngs)
84  self._seen_ranges[hrngs] = feat
85  return feat
86 
87 
88 def _parse_restraint_info(info):
89  """Convert RestraintInfo object to Python dict"""
90  d = {}
91  if info is None:
92  return d
93  info.set_was_used(True)
94  for typ in ('int', 'float', 'string', 'filename', 'floats', 'filenames',
95  'particle_indexes'):
96  for i in range(getattr(info, 'get_number_of_' + typ)()):
97  key = getattr(info, 'get_%s_key' % typ)(i)
98  value = getattr(info, 'get_%s_value' % typ)(i)
99  d[key] = value
100  return d
101 
102 
103 class _GaussianEMRestraint(ihm.restraint.EM3DRestraint):
104  """Handle an IMP.isd.GaussianEMRestraint"""
105 
106  def __init__(self, imp_restraint, info, modeled_assembly, system):
107  self._imp_restraint = imp_restraint
108  p = IMP.mmcif.metadata._GMMParser()
109  r = p.parse_file(info['filename'])
110  super(_GaussianEMRestraint, self).__init__(
111  dataset=r['dataset'],
112  assembly=modeled_assembly, # todo: fill in correct assembly
113  number_of_gaussians=r['number_of_gaussians'],
114  fitting_method='Gaussian mixture model')
115 
116  def add_model_fit(self, model):
117  info = _parse_restraint_info(self._imp_restraint.get_dynamic_info())
118  self.fits[model] = ihm.restraint.EM3DRestraintFit(
119  cross_correlation_coefficient=info['cross correlation'])
120 
121 
122 class _CrossLinkRestraint(ihm.restraint.CrossLinkRestraint):
123  def __init__(self, imp_restraint, info, modeled_assembly, system):
124  self._imp_restraint = imp_restraint
125  loc = ihm.location.InputFileLocation(
126  info['filename'], details='Crosslinks')
127  dataset = ihm.dataset.CXMSDataset(loc)
128  linker = ihm.ChemDescriptor(
129  auth_name=info['linker author name'],
130  chemical_name=info.get('linker chemical name'),
131  smiles=info.get('linker smiles'),
132  smiles_canonical=info.get('linker smiles canonical'),
133  inchi=info.get('linker inchi'),
134  inchi_key=info.get('linker inchi key'))
135  super(_CrossLinkRestraint, self).__init__(
136  dataset=dataset, linker=linker)
137  # Map from IMP/RMF chain names to ihm.Entity
138  cmap = dict((e.description, e) for e in system.entities.get_all())
139  dist = ihm.restraint.UpperBoundDistanceRestraint(info['linker length'])
140  asym = _AsymMapper(imp_restraint.get_model(), system)
141  self._add_all_links(IMP.RestraintSet.get_from(imp_restraint), cmap,
142  asym, dist)
143 
144  def _add_all_links(self, rset, cmap, asym, dist):
145  """Add info for each cross-link in the given RestraintSet"""
146  for link in rset.restraints:
147  # Recurse into any child RestraintSets
148  try:
149  child_rs = IMP.RestraintSet.get_from(link)
150  except ValueError:
151  child_rs = None
152  if child_rs:
153  self._add_all_links(child_rs, cmap, asym, dist)
154  else:
155  info = _parse_restraint_info(link.get_static_info())
156  # todo: handle ambiguous cross-links, fix residue numbering
157  r1 = cmap[info['protein1']].residue(info['residue1'])
158  r2 = cmap[info['protein2']].residue(info['residue2'])
159  ex_xl = ihm.restraint.ExperimentalCrossLink(residue1=r1,
160  residue2=r2)
161  self.experimental_cross_links.append([ex_xl])
162  # todo: handle multiple contributions
163  m = link.get_model()
164  endp1, endp2 = info['endpoints']
165  asym1 = asym[endp1]
166  asym2 = asym[endp2]
167  if _get_by_residue(m, endp1) and _get_by_residue(m, endp2):
168  cls = ihm.restraint.ResidueCrossLink
169  else:
170  cls = ihm.restraint.FeatureCrossLink
171  xl = cls(ex_xl, asym1=asym1, asym2=asym2, distance=dist,
172  restrain_all=False,
173  psi=_get_scale(m, info['psis'][0]),
174  sigma1=_get_scale(m, info['sigmas'][0]),
175  sigma2=_get_scale(m, info['sigmas'][1]))
176  self.cross_links.append(xl)
177 
178  def add_model_fit(self, model):
179  pass # todo
180 
181 
182 class _EM2DRestraint(ihm.restraint.EM2DRestraint):
183  """Handle an IMP.em2d.PCAFitRestraint"""
184 
185  def __init__(self, imp_restraint, info, modeled_assembly, system):
186  # todo: handle more than one image
187  self._imp_restraint = imp_restraint
188  loc = ihm.location.InputFileLocation(
189  info['image files'][0],
190  details="Electron microscopy class average")
191  dataset = ihm.dataset.EM2DClassDataset(loc)
192  super(_EM2DRestraint, self).__init__(
193  dataset=dataset,
194  assembly=modeled_assembly, # todo: fill in correct assembly
195  segment=False,
196  number_raw_micrographs=info['micrographs number'] or None,
197  pixel_size_width=info['pixel size'],
198  pixel_size_height=info['pixel size'],
199  image_resolution=info['resolution'],
200  number_of_projections=info['projection number'])
201 
202  def add_model_fit(self, model):
203  # todo: handle multiple images
204  nimage = 0
205  info = _parse_restraint_info(self._imp_restraint.get_dynamic_info())
206  ccc = info['cross correlation'][nimage]
207  transform = self._get_transformation(model, info, nimage)
208  rot = transform.get_rotation()
209  rm = [[e for e in rot.get_rotation_matrix_row(i)] for i in range(3)]
210  self.fits[model] = ihm.restraint.EM2DRestraintFit(
211  cross_correlation_coefficient=ccc, rot_matrix=rm,
212  tr_vector=transform.get_translation())
213 
214  def _get_transformation(self, model, info, nimage):
215  """Get the transformation that places the model on image nimage"""
216  r = info['rotation'][nimage * 4: nimage * 4 + 4]
217  t = info['translation'][nimage * 3: nimage * 3 + 3]
220 
221 
222 class _GeometricRestraint(ihm.restraint.GeometricRestraint):
223  """Base for all geometric restraints"""
224 
225  def __init__(self, imp_restraint, info, modeled_assembly, system):
226  self._imp_restraint = imp_restraint
227  asym = _AsymMapper(imp_restraint.get_model(), system)
228  super(_GeometricRestraint, self).__init__(
229  dataset=None,
230  geometric_object=self._geom_object,
231  feature=asym.get_feature(_get_input_pis(imp_restraint)),
232  distance=self._get_distance(info),
233  harmonic_force_constant=1. / info['sigma'],
234  restrain_all=True)
235 
236  def _get_distance(self, info):
237  pass
238 
239  def add_model_fit(self, model):
240  pass
241 
242 
243 class _ZAxialRestraint(_GeometricRestraint):
244  """Handle an IMP.npc.ZAxialRestraint"""
245  _geom_object = ihm.geometry.XYPlane()
246 
247  def _get_distance(self, info):
248  return ihm.restraint.LowerUpperBoundDistanceRestraint(
249  info['lower bound'], info['upper bound'])
250 
251 
252 class _SAXSRestraint(ihm.restraint.SASRestraint):
253  """Handle an IMP.saxs.Restraint"""
254 
255  def __init__(self, imp_restraint, info, modeled_assembly, system):
256  self._imp_restraint = imp_restraint
257  loc = ihm.location.InputFileLocation(
258  info['filename'], details='SAXS profile')
259  dataset = ihm.dataset.SASDataset(loc)
260  super(_SAXSRestraint, self).__init__(
261  dataset=dataset,
262  assembly=modeled_assembly, # todo: fill in correct assembly
263  segment=False, fitting_method='IMP SAXS restraint',
264  fitting_atom_type=info['form factor type'],
265  multi_state=False)
266 
267  def add_model_fit(self, model):
268  # We don't know the chi value; we only report a score
269  self.fits[model] = ihm.restraint.SASRestraintFit(chi_value=None)
270 
271 
272 class _RestraintMapper(object):
273  """Map IMP restraints to mmCIF objects"""
274  def __init__(self, system):
275  self._typemap = {
276  "IMP.isd.GaussianEMRestraint": _GaussianEMRestraint,
277  "IMP.pmi.CrossLinkingMassSpectrometryRestraint":
278  _CrossLinkRestraint,
279  "IMP.em2d.PCAFitRestraint": _EM2DRestraint,
280  "IMP.npc.ZAxialPositionRestraint": _ZAxialRestraint,
281  "IMP.saxs.Restraint": _SAXSRestraint}
282  self._system = system
283 
284  def handle(self, r, model, modeled_assembly):
285  """Handle an individual IMP restraint.
286  @return a wrapped version of the restraint if it is handled in
287  mmCIF, otherwise None."""
288  info = _parse_restraint_info(r.get_static_info())
289  if 'type' in info and info['type'] in self._typemap:
290  r = self._typemap[info['type']](r, info, modeled_assembly,
291  self._system)
292  r.add_model_fit(model)
293  return r
Simple 3D transformation class.
static bool get_is_setup(const IMP::ParticleAdaptor &p)
Definition: Residue.h:158
A decorator to associate a particle with a part of a protein/DNA/RNA.
Definition: Fragment.h:20
static bool get_is_setup(const IMP::ParticleAdaptor &p)
Definition: atom/Atom.h:245
Add scale parameter to particle.
Definition: Scale.h:24
static bool get_is_setup(Model *m, ParticleIndex pi)
Definition: Fragment.h:46
The standard decorator for manipulating molecular structures.
Ints get_index(const ParticlesTemp &particles, const Subset &subset, const Subsets &excluded)
static bool get_is_setup(const IMP::ParticleAdaptor &p)
Definition: Scale.h:29
3D rotation class.
Definition: Rotation3D.h:52
A decorator for a residue.
Definition: Residue.h:137
static bool get_is_setup(const IMP::ParticleAdaptor &p)
Definition: Chain.h:80
static bool get_is_setup(Model *m, ParticleIndex p)
Check if the particle has the needed attributes for a cast to succeed.
VectorD< 3 > Vector3D
Definition: VectorD.h:425
Store info for a chain of a protein.
Definition: Chain.h:61
Classes to extract metadata for various input files.