IMP logo
IMP Reference Guide  develop.5651aa123e,2024/07/26
The Integrative Modeling Platform
samplers.py
1 """@namespace IMP.pmi.samplers
2  Sampling of the system.
3 """
4 
5 import IMP
6 import IMP.core
7 from IMP.pmi.tools import get_restraint_set
8 
9 
10 class _SerialReplicaExchange:
11  """Dummy replica exchange class used in non-MPI builds.
12  It should act similarly to IMP.mpi.ReplicaExchange
13  on a single processor.
14  """
15  def __init__(self):
16  self.__params = {}
17 
18  def get_number_of_replicas(self):
19  return 1
20 
21  def create_temperatures(self, tmin, tmax, nrep):
22  return [tmin]
23 
24  def get_my_index(self):
25  return 0
26 
27  def set_my_parameter(self, key, val):
28  self.__params[key] = val
29 
30  def get_my_parameter(self, key):
31  return self.__params[key]
32 
33  def get_friend_index(self, step):
34  return 0
35 
36  def get_friend_parameter(self, key, findex):
37  return self.get_my_parameter(key)
38 
39  def do_exchange(self, myscore, fscore, findex):
40  return False
41 
42  def set_was_used(self, was_used):
43  self.was_used = was_used
44 
45 
46 class MonteCarlo:
47  """Sample using Monte Carlo"""
48 
49  # check that isd is installed
50  try:
51  import IMP.isd
52  isd_available = True
53  except ImportError:
54  isd_available = False
55 
56  def __init__(self, model, objects=None, temp=1.0, filterbyname=None,
57  score_moved=False):
58  """Setup Monte Carlo sampling
59  @param model The IMP Model
60  @param objects What to sample (a list of Movers)
61  @param temp The MC temperature
62  @param filterbyname Not used
63  @param score_moved If True, attempt to speed up sampling by
64  caching scoring function terms on particles that didn't move
65  """
66  self.losp = [
67  "Rigid_Bodies",
68  "Floppy_Bodies",
69  "Nuisances",
70  "X_coord",
71  "Weights"
72  "Surfaces"]
73  self.simulated_annealing = False
74  self.selfadaptive = False
75  # that is -1 because mc has not yet run
76  self.nframe = -1
77  self.temp = temp
78  self.mvs = []
79  self.mvslabels = []
80  self.label = "None"
81  self.model = model
82  self.movers_data = {}
83 
84  self.mvs = objects
85 
86  # SerialMover
87  self.smv = IMP.core.SerialMover(self.mvs)
88 
89  self.mc = IMP.core.MonteCarlo(self.model)
90  self.mc.set_scoring_function(get_restraint_set(self.model))
91  self.mc.set_return_best(False)
92  self.mc.set_score_moved(score_moved)
93  self.mc.set_kt(self.temp)
94  self.mc.add_mover(self.smv)
95 
96  def set_kt(self, temp):
97  self.temp = temp
98  self.mc.set_kt(temp)
99 
100  def get_mc(self):
101  return self.mc
102 
103  def set_scoring_function(self, objectlist):
104  rs = IMP.RestraintSet(self.model, 1.0, 'sfo')
105  for ob in objectlist:
106  rs.add_restraint(ob.get_restraint())
108  self.mc.set_scoring_function(sf)
109 
110  def set_simulated_annealing(
111  self,
112  min_temp,
113  max_temp,
114  min_temp_time,
115  max_temp_time):
116  self.simulated_annealing = True
117  self.tempmin = min_temp
118  self.tempmax = max_temp
119  self.timemin = min_temp_time
120  self.timemax = max_temp_time
121 
122  def set_self_adaptive(self, isselfadaptive=True):
123  self.selfadaptive = isselfadaptive
124 
126  '''
127  Return a dictionary with the mover parameters for nuisance parameters
128  '''
129  output = {}
130  for i in range(self.get_number_of_movers()):
131  mv = self.smv.get_mover(i)
132  name = mv.get_name()
133  if "Nuisances" in name:
134  stepsize = IMP.core.NormalMover.get_from(mv).get_sigma()
135  output[name] = stepsize
136  return output
137 
138  def get_number_of_movers(self):
139  return len(self.smv.get_movers())
140 
141  def get_particle_types(self):
142  return self.losp
143 
144  def optimize(self, nstep):
145  self.nframe += 1
146  self.mc.optimize(nstep * self.get_number_of_movers())
147 
148  # apply simulated annealing protocol
149  if self.simulated_annealing:
150  self.temp = self.temp_simulated_annealing()
151  self.mc.set_kt(self.temp)
152 
153  # apply self adaptive protocol
154  if self.selfadaptive:
155  for i, mv in enumerate(self.mvs):
156 
157  mvacc = mv.get_number_of_accepted()
158  mvprp = mv.get_number_of_proposed()
159  if mv not in self.movers_data:
160  accept = float(mvacc) / float(mvprp)
161  self.movers_data[mv] = (mvacc, mvprp)
162  else:
163  oldmvacc, oldmvprp = self.movers_data[mv]
164  accept = float(mvacc-oldmvacc) / float(mvprp-oldmvprp)
165  self.movers_data[mv] = (mvacc, mvprp)
166  if accept < 0.05:
167  accept = 0.05
168  if accept > 1.0:
169  accept = 1.0
170 
171  if isinstance(mv, IMP.core.NormalMover):
172  stepsize = mv.get_sigma()
173  if 0.4 > accept or accept > 0.6:
174  mv.set_sigma(stepsize * 2 * accept)
175 
176  if isinstance(mv, IMP.isd.WeightMover):
177  stepsize = mv.get_radius()
178  if 0.4 > accept or accept > 0.6:
179  mv.set_radius(stepsize * 2 * accept)
180 
181  if isinstance(mv, IMP.core.RigidBodyMover):
182  mr = mv.get_maximum_rotation()
183  mt = mv.get_maximum_translation()
184  if 0.4 > accept or accept > 0.6:
185  mv.set_maximum_rotation(mr * 2 * accept)
186  mv.set_maximum_translation(mt * 2 * accept)
187 
188  if isinstance(mv, IMP.pmi.TransformMover):
189  mr = mv.get_maximum_rotation()
190  mt = mv.get_maximum_translation()
191  if 0.4 > accept or accept > 0.6:
192  mv.set_maximum_rotation(mr * 2 * accept)
193  mv.set_maximum_translation(mt * 2 * accept)
194 
195  if isinstance(mv, IMP.core.BallMover):
196  mr = mv.get_radius()
197  if 0.4 > accept or accept > 0.6:
198  mv.set_radius(mr * 2 * accept)
199 
200  def get_nuisance_movers(self, nuisances, maxstep):
201  mvs = []
202  for nuisance in nuisances:
203  print(nuisance, maxstep)
204  mvs.append(
205  IMP.core.NormalMover([nuisance],
206  IMP.FloatKeys([IMP.FloatKey("nuisance")]),
207  maxstep))
208  return mvs
209 
210  def get_rigid_body_movers(self, rbs, maxtrans, maxrot):
211  mvs = []
212  for rb in rbs:
213  mvs.append(IMP.core.RigidBodyMover(rb.get_model(), rb,
214  maxtrans, maxrot))
215  return mvs
216 
217  def get_super_rigid_body_movers(self, rbs, maxtrans, maxrot):
218  mvs = []
219  for rb in rbs:
220  if len(rb) == 2:
221  # normal Super Rigid Body
222  srbm = IMP.pmi.TransformMover(self.model, maxtrans, maxrot)
223  elif len(rb) == 3:
224  if isinstance(rb[2], tuple) and len(rb[2]) == 3 \
225  and isinstance(rb[2][0], float) \
226  and isinstance(rb[2][1], float) \
227  and isinstance(rb[2][2], float):
228  # super rigid body with 2D rotation, rb[2] is the axis
229  srbm = IMP.pmi.TransformMover(
230  self.model, IMP.algebra.Vector3D(rb[2]), maxtrans,
231  maxrot)
232  else:
233  print(
234  "Setting up a super rigid body with wrong parameters")
235  raise
236 
237  for xyz in rb[0]:
238  srbm.add_xyz_particle(xyz)
239  for rb in rb[1]:
240  srbm.add_rigid_body_particle(rb)
241  mvs.append(srbm)
242  return mvs
243 
244  def get_floppy_body_movers(self, fbs, maxtrans):
245  mvs = []
246  for fb in fbs:
247  # check is that is a rigid body member:
249  # if so force the particles to move anyway
250  floatkeys = \
252  for fk in floatkeys:
253  fb.set_is_optimized(fk, True)
254  mvs.append(
255  IMP.core.BallMover(fb.get_model(), fb,
256  IMP.FloatKeys(floatkeys),
257  maxtrans))
258  else:
259  # otherwise use the normal ball mover
260  mvs.append(IMP.core.BallMover(fb.get_model(), fb, maxtrans))
261  return mvs
262 
263  def get_X_movers(self, fbs, maxtrans):
264  mvs = []
265  Xfloatkey = IMP.core.XYZ.get_xyz_keys()[0]
266  for fb in fbs:
267  # check is that is a rigid body member:
269  raise ValueError("particle is part of a rigid body")
270  else:
271  # otherwise use the normal ball mover
272  mvs.append(IMP.core.NormalMover([fb], [Xfloatkey], maxtrans))
273  return mvs
274 
275  def get_weight_movers(self, weights, maxstep):
276  mvs = []
277  for weight in weights:
278  if weight.get_number_of_weights() > 1:
279  mvs.append(IMP.isd.WeightMover(weight, maxstep))
280  return mvs
281 
282  def get_surface_movers(self, surfaces, maxtrans, maxrot, refprob):
283  mvs = []
284  for surface in surfaces:
285  mvs.append(IMP.core.SurfaceMover(surface, maxtrans, maxrot,
286  refprob))
287  return mvs
288 
289  def temp_simulated_annealing(self):
290  if self.nframe % (self.timemin + self.timemax) < self.timemin:
291  value = 0.0
292  else:
293  value = 1.0
294  temp = self.tempmin + (self.tempmax - self.tempmin) * value
295  return temp
296 
297  def set_label(self, label):
298  self.label = label
299 
300  def get_frame_number(self):
301  return self.nframe
302 
303  def get_output(self):
304  output = {}
305  for i, mv in enumerate(self.smv.get_movers()):
306  mvname = mv.get_name()
307  mvacc = mv.get_number_of_accepted()
308  mvprp = mv.get_number_of_proposed()
309  try:
310  mvacr = float(mvacc) / float(mvprp)
311  except: # noqa: E722
312  mvacr = 0.0
313  output["MonteCarlo_Acceptance_" +
314  mvname + "_" + str(i)] = str(mvacr)
315  if "Nuisances" in mvname:
316  output["MonteCarlo_StepSize_" + mvname + "_" + str(i)] = \
317  str(IMP.core.NormalMover.get_from(mv).get_sigma())
318  if "Weights" in mvname:
319  output["MonteCarlo_StepSize_" + mvname + "_" + str(i)] = \
320  str(IMP.isd.WeightMover.get_from(mv).get_radius())
321  output["MonteCarlo_Temperature"] = str(self.mc.get_kt())
322  output["MonteCarlo_Nframe"] = str(self.nframe)
323  return output
324 
325 
327  """Sample using molecular dynamics"""
328 
329  def __init__(self, model, objects, kt, gamma=0.01, maximum_time_step=1.0,
330  sf=None):
331  """Setup MD
332  @param model The IMP Model
333  @param objects What to sample. Use flat list of particles
334  @param kt Temperature
335  @param gamma Viscosity parameter
336  @param maximum_time_step MD max time step
337  """
338  self.model = model
339 
340  # check if using PMI1 objects dictionary, or just list of particles
341  try:
342  for obj in objects:
343  psamp = obj.get_particles_to_sample()
344  to_sample = psamp['Floppy_Bodies_SimplifiedModel'][0]
345  except: # noqa: E722
346  to_sample = objects
347 
349  self.model, to_sample, kt/0.0019872041, gamma)
350  self.md = IMP.atom.MolecularDynamics(self.model)
351  self.md.set_maximum_time_step(maximum_time_step)
352  if sf:
353  self.md.set_scoring_function(sf)
354  else:
355  self.md.set_scoring_function(get_restraint_set(self.model))
356  self.md.add_optimizer_state(self.ltstate)
357  self.simulated_annealing = False
358  self.nframe = -1
359 
360  def set_kt(self, kt):
361  temp = kt/0.0019872041
362  self.ltstate.set_temperature(temp)
363  self.md.assign_velocities(temp)
364 
365  def set_simulated_annealing(self, min_temp, max_temp, min_temp_time,
366  max_temp_time):
367  self.simulated_annealing = True
368  self.tempmin = min_temp
369  self.tempmax = max_temp
370  self.timemin = min_temp_time
371  self.timemax = max_temp_time
372 
373  def temp_simulated_annealing(self):
374  if self.nframe % (self.timemin + self.timemax) < self.timemin:
375  value = 0.0
376  else:
377  value = 1.0
378  temp = self.tempmin + (self.tempmax - self.tempmin) * value
379  return temp
380 
381  def set_gamma(self, gamma):
382  self.ltstate.set_gamma(gamma)
383 
384  def optimize(self, nsteps):
385  # apply simulated annealing protocol
386  self.nframe += 1
387  if self.simulated_annealing:
388  self.temp = self.temp_simulated_annealing()
389  self.set_kt(self.temp)
390  self.md.optimize(nsteps)
391 
392  def get_output(self):
393  output = {}
394  output["MolecularDynamics_KineticEnergy"] = \
395  str(self.md.get_kinetic_energy())
396  return output
397 
398 
400  """Sample using conjugate gradients"""
401 
402  def __init__(self, model, objects):
403  self.model = model
404  self.nframe = -1
405  self.cg = IMP.core.ConjugateGradients(self.model)
406  self.cg.set_scoring_function(get_restraint_set(self.model))
407 
408  def set_label(self, label):
409  self.label = label
410 
411  def get_frame_number(self):
412  return self.nframe
413 
414  def optimize(self, nstep):
415  self.nframe += 1
416  self.cg.optimize(nstep)
417 
418  def set_scoring_function(self, objectlist):
419  rs = IMP.RestraintSet(self.model, 1.0, 'sfo')
420  for ob in objectlist:
421  rs.add_restraint(ob.get_restraint())
423  self.cg.set_scoring_function(sf)
424 
425  def get_output(self):
426  output = {}
427  output["ConjugatedGradients_Nframe"] = str(self.nframe)
428  return output
429 
430 
432  """Sample using replica exchange"""
433 
434  def __init__(self, model, tempmin, tempmax, samplerobjects, test=True,
435  replica_exchange_object=None):
436  '''
437  samplerobjects can be a list of MonteCarlo or MolecularDynamics
438  '''
439 
440  self.model = model
441  self.samplerobjects = samplerobjects
442  # min and max temperature
443  self.TEMPMIN_ = tempmin
444  self.TEMPMAX_ = tempmax
445 
446  if replica_exchange_object is None:
447  # initialize Replica Exchange class
448  try:
449  import IMP.mpi
450  print('ReplicaExchange: MPI was found. '
451  'Using Parallel Replica Exchange')
452  self.rem = IMP.mpi.ReplicaExchange()
453  except ImportError:
454  print('ReplicaExchange: Could not find MPI. '
455  'Using Serial Replica Exchange')
456  self.rem = _SerialReplicaExchange()
457 
458  else:
459  # get the replica exchange class instance from elsewhere
460  print('got existing rex object')
461  self.rem = replica_exchange_object
462 
463  # get number of replicas
464  nproc = self.rem.get_number_of_replicas()
465 
466  if nproc % 2 != 0 and not test:
467  raise Exception(
468  "number of replicas has to be even. "
469  "set test=True to run with odd number of replicas.")
470  # create array of temperatures, in geometric progression
471  temp = self.rem.create_temperatures(
472  self.TEMPMIN_,
473  self.TEMPMAX_,
474  nproc)
475  # get replica index
476  self.temperatures = temp
477 
478  myindex = self.rem.get_my_index()
479  # set initial value of the parameter (temperature) to exchange
480  self.rem.set_my_parameter("temp", [self.temperatures[myindex]])
481  for so in self.samplerobjects:
482  so.set_kt(self.temperatures[myindex])
483  self.nattempts = 0
484  self.nmintemp = 0
485  self.nmaxtemp = 0
486  self.nsuccess = 0
487 
488  def get_temperatures(self):
489  return self.temperatures
490 
491  def get_my_temp(self):
492  return self.rem.get_my_parameter("temp")[0]
493 
494  def get_my_index(self):
495  return self.rem.get_my_index()
496 
497  def swap_temp(self, nframe, score=None):
498  if score is None:
499  score = self.model.evaluate(False)
500  # get my replica index and temperature
501  _ = self.rem.get_my_index()
502  mytemp = self.rem.get_my_parameter("temp")[0]
503 
504  if mytemp == self.TEMPMIN_:
505  self.nmintemp += 1
506 
507  if mytemp == self.TEMPMAX_:
508  self.nmaxtemp += 1
509 
510  # score divided by kbt
511  myscore = score / mytemp
512 
513  # get my friend index and temperature
514  findex = self.rem.get_friend_index(nframe)
515  ftemp = self.rem.get_friend_parameter("temp", findex)[0]
516  # score divided by kbt
517  fscore = score / ftemp
518 
519  # try exchange
520  flag = self.rem.do_exchange(myscore, fscore, findex)
521 
522  self.nattempts += 1
523  # if accepted, change temperature
524  if (flag):
525  for so in self.samplerobjects:
526  so.set_kt(ftemp)
527  self.nsuccess += 1
528 
529  def get_output(self):
530  output = {}
531  if self.nattempts != 0:
532  output["ReplicaExchange_SwapSuccessRatio"] = str(
533  float(self.nsuccess) / self.nattempts)
534  output["ReplicaExchange_MinTempFrequency"] = str(
535  float(self.nmintemp) / self.nattempts)
536  output["ReplicaExchange_MaxTempFrequency"] = str(
537  float(self.nmaxtemp) / self.nattempts)
538  else:
539  output["ReplicaExchange_SwapSuccessRatio"] = str(0)
540  output["ReplicaExchange_MinTempFrequency"] = str(0)
541  output["ReplicaExchange_MaxTempFrequency"] = str(0)
542  output["ReplicaExchange_CurrentTemp"] = str(self.get_my_temp())
543  return output
544 
545 
546 class MPI_values:
547  def __init__(self, replica_exchange_object=None):
548  """Query values (ie score, and others)
549  from a set of parallel jobs"""
550 
551  if replica_exchange_object is None:
552  # initialize Replica Exchange class
553  try:
554  import IMP.mpi
555  print('MPI_values: MPI was found. '
556  'Using Parallel Replica Exchange')
557  self.rem = IMP.mpi.ReplicaExchange()
558  except ImportError:
559  print('MPI_values: Could not find MPI. '
560  'Using Serial Replica Exchange')
561  self.rem = _SerialReplicaExchange()
562 
563  else:
564  # get the replica exchange class instance from elsewhere
565  print('got existing rex object')
566  self.rem = replica_exchange_object
567 
568  def set_value(self, name, value):
569  self.rem.set_my_parameter(name, [value])
570 
571  def get_values(self, name):
572  values = []
573  for i in range(self.rem.get_number_of_replicas()):
574  v = self.rem.get_friend_parameter(name, i)[0]
575  values.append(v)
576  return values
577 
578  def get_percentile(self, name):
579  value = self.rem.get_my_parameter(name)[0]
580  values = sorted(self.get_values(name))
581  ind = values.index(value)
582  percentile = float(ind)/len(values)
583  return percentile
def __init__
samplerobjects can be a list of MonteCarlo or MolecularDynamics
Definition: samplers.py:434
A Monte Carlo optimizer.
Definition: MonteCarlo.h:44
A class to implement Hamiltonian Replica Exchange.
Maintains temperature during molecular dynamics.
Sample using molecular dynamics.
Definition: samplers.py:326
def get_nuisance_movers_parameters
Return a dictionary with the mover parameters for nuisance parameters.
Definition: samplers.py:125
Miscellaneous utilities.
Definition: tools.py:1
Modify the transformation of a rigid body.
Simple conjugate gradients optimizer.
Sample using conjugate gradients.
Definition: samplers.py:399
Create a scoring function on a list of restraints.
Move continuous particle variables by perturbing them within a ball.
Modify a surface orientation.
Definition: SurfaceMover.h:37
static FloatKeys get_internal_coordinate_keys()
Definition: rigid_bodies.h:727
Object used to hold a set of restraints.
Definition: RestraintSet.h:41
Simple molecular dynamics simulator.
Code that uses the MPI parallel library.
A mover that perturbs a Weight particle.
Definition: WeightMover.h:20
Modify the transformation of a rigid body.
def __init__
Setup Monte Carlo sampling.
Definition: samplers.py:56
Modify a set of continuous variables using a normal distribution.
Definition: NormalMover.h:23
Basic functionality that is expected to be used by a wide variety of IMP users.
Sample using Monte Carlo.
Definition: samplers.py:46
The general base class for IMP exceptions.
Definition: exception.h:48
VectorD< 3 > Vector3D
Definition: VectorD.h:408
static const FloatKeys & get_xyz_keys()
Get a vector containing the keys for x,y,z.
Applies a list of movers one at a time.
Definition: SerialMover.h:26
static bool get_is_setup(const IMP::ParticleAdaptor &p)
Definition: rigid_bodies.h:770
Sample using replica exchange.
Definition: samplers.py:431
Inferential scoring building on methods developed as part of the Inferential Structure Determination ...