IMP logo
IMP Reference Guide  develop.7f24702912,2026/04/21
The Integrative Modeling Platform
pmi/restraints/__init__.py
1 """@namespace IMP.pmi.restraints
2  Classes to handle different kinds of restraints.
3 
4 PMI restraints generally wrap IMP restraints. Typical features in PMI restraints are:
5  - Easy setup: for example, you can usually create one with a PMI [Molecule](@ref IMP::pmi::topology::Molecule) or a slice from one.
6  - Fast setup from data files. For example you can set up the [CrossLinkingMassSpectrometryRestraint](@ref IMP::pmi::restraints::crosslinking::CrossLinkingMassSpectrometryRestraint) by reading in a cross-link file into a [database](@ref IMP::pmi::io::crosslink::CrossLinkDataBase).
7  - Useful output: reporting functions which are put into log files when running [ReplicaExchange](@ref IMP::pmi::macros::ReplicaExchange).
8 """ # noqa: E501
9 
10 import IMP
11 import IMP.pmi
12 import IMP.pmi.tools
13 from IMP.pmi.tools import RestraintStatScorer
14 
15 
16 class RestraintBase:
17  _include_in_rmf = False
18 
19  """Base class for PMI restraints, which wrap `IMP.Restraint`(s)."""
20 
21  def __init__(self, m, name=None, label=None, weight=1.,
22  restraint_set_class=IMP.RestraintSet):
23  """Constructor.
24  @param m The model object
25  @param name The name of the primary restraint set that is wrapped.
26  This is used for outputs and particle/restraint names
27  and should be set by the child class.
28  @param label A unique label to be used in outputs and
29  particle/restraint names.
30  @param weight The weight to apply to all internal restraints.
31  @param restraint_set_class The class to use for the restraint set
32  """
33  self.model = m
34  self.restraint_sets = []
35  self._label_is_set = False
36  self.weight = weight
37  self._label = None
38  self._label_suffix = ""
39  self.set_label(label)
40 
41  if not name:
42  self.name = self.__class__.__name__
43  else:
44  self.name = str(name)
45 
46  self.rs = self._create_restraint_set(name=None,
47  cls=restraint_set_class)
48 
49  def set_label(self, label):
50  """Set the unique label used in outputs and particle/restraint names.
51  @param label Label
52  """
53  if self._label_is_set:
54  raise ValueError("Label has already been set, or restraint has "
55  "already been added to model.")
56  if not label:
57  self._label = ""
58  self._label_suffix = ""
59  else:
60  self._label = str(label)
61  self._label_suffix = "_" + self._label
62  self._label_is_set = True
63 
64  @property
65  def label(self):
66  return self._label
67 
68  def set_weight(self, weight):
69  """Set the weight to apply to all internal restraints.
70  @param weight Weight
71  """
72  self.weight = weight
73  for rs in self.restraint_sets:
74  rs.set_weight(self.weight)
75 
76  def add_to_model(self):
77  """Add the restraint to the model."""
78  self._label_is_set = True
79  for rs in self.restraint_sets:
81  self.model, rs, add_to_rmf=self._include_in_rmf)
82 
83  def evaluate(self):
84  """Evaluate the score of the restraint."""
85  self._label_is_set = True
86  return self.weight * self.rs.unprotected_evaluate(None)
87 
88  def get_restraint_set(self):
89  """Get the primary restraint set."""
90  self._label_is_set = True
91  return self.rs
92 
93  def get_restraint(self):
94  """Get the primary restraint set. Identical to `get_restraint_set`."""
95  return self.get_restraint_set()
96 
97  def get_restraint_for_rmf(self):
98  """Get the restraint for visualization in an RMF file."""
99  self._label_is_set = True
100  return self.rs
101 
102  def get_particles_to_sample(self):
103  """Get any created particles which should be sampled."""
104  self._label_is_set = True
105  return {}
106 
107  def get_output(self):
108  """Get outputs to write to stat files."""
109  self._label_is_set = True
110  scorer = RestraintStatScorer("_TotalScore", self, self.rs)
111  suffix = "_Score" + self._label_suffix
112  scorers = [RestraintStatScorer(rs.get_name() + suffix, self, rs)
113  for rs in self.restraint_sets] + [scorer]
114 
115  return lambda jm: {s.name: str(s(jm)) for s in scorers}
116 
117  def _create_restraint_set(self, name=None, cls=IMP.RestraintSet):
118  """Create ``IMP.RestraintSet``."""
119  if not name:
120  name = self.name
121  else:
122  name = self.name + "_" + str(name)
123  rs = cls(self.model, name)
124  rs.set_weight(self.weight)
125  self.restraint_sets.append(rs)
126  rs.set_was_used(True)
127  return rs
128 
129 
130 class _RestraintNuisanceMixin:
131 
132  """Mix-in to add nuisance particle creation functionality to restraint.
133 
134  This class must only be inherited if also inheriting
135  IMP.pmi.restraints.RestraintBase.
136  """
137 
138  def __init__(self, *args, **kwargs):
139  super().__init__(*args, **kwargs)
140  self.sampled_nuisances = {}
141  self.nuisances = {}
142 
143  def _create_nuisance(self, init_val, min_val, max_val, max_trans, name,
144  is_sampled=False):
145  """Create nuisance particle.
146  @param init_val Initial value of nuisance
147  @param min_val Minimum value of nuisance
148  @param max_val Maximum value of nuisance
149  @param max_trans Maximum move to apply to nuisance
150  @param name Name of particle
151  @param is_sampled Nuisance is a sampled particle
152  @see IMP.pmi.tools.SetupNuisance
153  """
154  nuis = IMP.pmi.tools.SetupNuisance(
155  self.model, init_val, min_val, max_val,
156  isoptimized=is_sampled).get_particle()
157  nuis_name = self.name + "_" + name
158  nuis.set_name(nuis_name)
159  self.nuisances[nuis_name] = nuis
160  if is_sampled:
161  self.sampled_nuisances[nuis_name] = (nuis, max_trans)
162  return nuis
163 
164  def get_particles_to_sample(self):
165  """Get any created particles which should be sampled."""
166  ps = super().get_particles_to_sample()
167  for name, (nuis, max_trans) in self.sampled_nuisances.items():
168  ps["Nuisances_" + name + self._label_suffix] = ([nuis], max_trans)
169  return ps
170 
171  def get_output(self):
172  """Get outputs to write to stat files."""
173  super_output = super().get_output()
174 
175  def score(jm):
176  output = super_output(jm)
177  for nuis_name, nuis in self.nuisances.items():
178  output[nuis_name + self._label_suffix] = str(nuis.get_scale())
179  return output
180  return score
181 
182 
183 class _NuisancesBase:
184 
185  """This base class is used to provide nuisance setup and interface
186  for the ISD cross-link restraints"""
187 
188  sigma_dictionary = {}
189  psi_dictionary = {}
190 
191  def create_length(self):
192  """Create a nuisance on the length of the cross-link."""
193  lengthinit = 10.0
194  self.lengthissampled = True
195  lengthminnuis = 0.0000001
196  lengthmaxnuis = 1000.0
197  lengthmin = 6.0
198  lengthmax = 30.0
199  length = IMP.pmi.tools.SetupNuisance(self.m, lengthinit,
200  lengthminnuis, lengthmaxnuis,
201  self.lengthissampled
202  ).get_particle()
203  self.rslen.add_restraint(
205  self.m,
206  length,
207  1000000000.0,
208  lengthmax,
209  lengthmin))
210 
211  def create_sigma(self, resolution):
212  """Create a nuisance on the structural uncertainty."""
213  if isinstance(resolution, str):
214  sigmainit = 2.0
215  else:
216  sigmainit = resolution + 2.0
217  self.sigmaissampled = True
218  sigmaminnuis = 0.0000001
219  sigmamaxnuis = 1000.0
220  sigmamin = 0.01
221  sigmamax = 100.0
222  sigmatrans = 0.5
223  sigma = IMP.pmi.tools.SetupNuisance(self.m, sigmainit, sigmaminnuis,
224  sigmamaxnuis, self.sigmaissampled
225  ).get_particle()
226  self.sigma_dictionary[resolution] = (
227  sigma,
228  sigmatrans,
229  self.sigmaissampled)
230  self.rssig.add_restraint(
232  self.m,
233  sigma,
234  1000000000.0,
235  sigmamax,
236  sigmamin))
237  # self.rssig.add_restraint(IMP.isd.JeffreysRestraint(self.sigma))
238 
239  def get_sigma(self, resolution):
240  """Get the nuisance on structural uncertainty."""
241  if resolution not in self.sigma_dictionary:
242  self.create_sigma(resolution)
243  return self.sigma_dictionary[resolution]
244 
245  def create_psi(self, value):
246  """Create a nuisance on the inconsistency."""
247  if isinstance(value, str):
248  psiinit = 0.5
249  else:
250  psiinit = value
251  self.psiissampled = True
252  psiminnuis = 0.0000001
253  psimaxnuis = 0.4999999
254  psimin = 0.01
255  psimax = 0.49
256  psitrans = 0.1
257  psi = IMP.pmi.tools.SetupNuisance(self.m, psiinit,
258  psiminnuis, psimaxnuis,
259  self.psiissampled).get_particle()
260  self.psi_dictionary[value] = (
261  psi,
262  psitrans,
263  self.psiissampled)
264  self.rspsi.add_restraint(
266  self.m,
267  psi,
268  1000000000.0,
269  psimax,
270  psimin))
271  self.rspsi.add_restraint(IMP.isd.JeffreysRestraint(self.m, psi))
272 
273  def get_psi(self, value):
274  """Get the nuisance on the inconsistency."""
275  if value not in self.psi_dictionary:
276  self.create_psi(value)
277  return self.psi_dictionary[value]
Miscellaneous utilities.
Definition: pmi/tools.py:1
Uniform distribution with harmonic boundaries.
Definition: UniformPrior.h:22
Object used to hold a set of restraints.
Definition: RestraintSet.h:41
def add_restraint_to_model
Add a PMI restraint to the model.
Definition: pmi/tools.py:84
Python classes to represent, score, sample and analyze models.