IMP logo
IMP Reference Guide  develop.345e71cb9a,2026/08/09
The Integrative Modeling Platform
macros.py
1 """@namespace IMP.pmi.macros
2 Protocols for sampling structures and analyzing them.
3 """
4 
5 import IMP
6 import IMP.pmi.tools
7 import IMP.pmi.samplers
8 import IMP.pmi.output
9 import IMP.pmi.analysis
10 import IMP.pmi.io
11 import IMP.pmi.alphabets
12 import IMP.rmf
13 import IMP.isd
14 import IMP.pmi.dof
15 import os
16 from pathlib import Path
17 import glob
18 from operator import itemgetter
19 from collections import defaultdict
20 import numpy as np
21 import itertools
22 import warnings
23 import math
24 
25 import pickle
26 
27 
28 class _MockMPIValues:
29  """Replace samplers.MPI_values when in test mode"""
30  def get_percentile(self, name):
31  return 0.
32 
33 
34 class _RMFRestraints:
35  """All restraints that are written out to the RMF file"""
36  def __init__(self, model, user_restraints):
37  self._rmf_rs = IMP.pmi.tools.get_restraint_set(model, rmf=True)
38  self._user_restraints = user_restraints if user_restraints else []
39 
40  def __len__(self):
41  return (len(self._user_restraints)
42  + self._rmf_rs.get_number_of_restraints())
43 
44  def __bool__(self):
45  return len(self) > 0
46 
47  def __getitem__(self, i):
48  class FakePMIWrapper:
49  def __init__(self, r):
50  self.r = IMP.RestraintSet.get_from(r)
51 
52  def get_restraint(self):
53  return self.r
54 
55  lenuser = len(self._user_restraints)
56  if 0 <= i < lenuser:
57  return self._user_restraints[i]
58  elif 0 <= i - lenuser < self._rmf_rs.get_number_of_restraints():
59  r = self._rmf_rs.get_restraint(i - lenuser)
60  return FakePMIWrapper(r)
61  else:
62  raise IndexError("Out of range")
63 
64 
65 class _StatFile:
66  """All output statistics objects to add to stat files and/or RMFs"""
67  def __init__(self, output_objects, rmf_output_objects):
68  self.objects = self.rmf_objects = None
69  # Don't modify user-provided objects; use a copy instead
70  if output_objects is not None:
71  self.objects = output_objects[:]
72  if rmf_output_objects is not None:
73  self.rmf_objects = rmf_output_objects[:]
74 
75  def append(self, obj):
76  if self.objects is not None:
77  self.objects.append(obj)
78  if self.rmf_objects is not None:
79  self.rmf_objects.append(obj)
80 
81 
82 class _RestartInfo:
83  """Parameters for writing restart files"""
84  def __init__(self, frames, restart_dir):
85  self._frames = frames
86  self._restart_dir = restart_dir
87  # Number of the restart; this will be incremented every time we
88  # run _RestartRun.execute_macro()
89  self._number = 0
90 
91  def _write_frame(self, rex, frame, myindex, rex_stats):
92  """Possibly write a restart file for the replica exchange run `rex`"""
93  if frame % self._frames != 0:
94  return
95  print(f'--- writing restart file at frame {frame}')
96  d = Path(rex.vars["global_output_directory"]) / self._restart_dir
97  d.mkdir(exist_ok=True)
98  fname = d / f'restart.{myindex}.pck'
99  # Keep a backup of the previous restart
100  if fname.exists():
101  prev = d / f'restart.{myindex}.prev.pck'
102  fname.replace(prev)
103  else:
104  self._write_readme(d / 'README.txt')
105 
106  r = _RestartRun(rex, frame, rex_stats)
107  with open(fname, 'wb') as fh:
108  pickle.dump(r, fh)
109 
110  def _write_readme(self, fname):
111  with open(fname, 'w') as fh:
112  fh.write("""
113 This directory contains files that can be used to restart an interrupted
114 simulation. To do so, use the IMP.pmi.macros.restart_replica_exchange function.
115 
116 Restart files are Python pickles that contain the current configuration of
117 the IMP model (e.g. coordinates), the scoring function, and the PMI sampler
118 (e.g. Monte Carlo movers and acceptance statistics). Each replica has its own
119 internal state and thus its own restart file. Files for the previous restart
120 are also kept (with a .prev.pck extension) in case the most recent restart
121 is corrupted.
122 
123 Restart files contain IMP internal state and so will probably not work with
124 a different version of IMP, or on a different operating system. As with all
125 Python pickles, these files may contain executable Python code and so you
126 should not use a restart file from an untrusted source.
127 """)
128 
129  restarted = property(lambda self: self._number > 0,
130  doc="True iff this simulation has been restarted")
131 
132 
133 class _RestartRun:
134  """Information about a restarted simulation (usually pickled)"""
135  def __init__(self, rex, frame, rex_stats):
136  # Ensure that IMP::Model is unpickled before the PMI rex macro so that
137  # model IDs are resolved correctly
138  self._pck_info = (rex.model, rex)
139  self._rstate = IMP.random_number_generator.get_state()
140  self._frame = frame
141  self._rex_stats = rex_stats
142 
143  def execute_macro(self):
144  """Restart the interrupted replica exchange simulation"""
145  m, rex = self._pck_info
146  IMP.random_number_generator.set_state(self._rstate)
147  rex._restart._number += 1
148  rex._restart_from_frame = self._frame
149  rex._rex_stats = self._rex_stats
150  return rex.execute_macro()
151 
152  def get_number_of_replicas(self):
153  rex = self._pck_info[1]
154  return rex.replica_exchange_object.get_number_of_replicas()
155 
156 
158  """A macro to help setup and run replica exchange.
159  Supports Monte Carlo and molecular dynamics.
160  Produces trajectory RMF files, best PDB structures,
161  and output stat files.
162  """
163  def __init__(self, model, root_hier,
164  monte_carlo_sample_objects=None,
165  molecular_dynamics_sample_objects=None,
166  output_objects=[],
167  rmf_output_objects=None,
168  monte_carlo_temperature=1.0,
169  simulated_annealing=False,
170  simulated_annealing_minimum_temperature=1.0,
171  simulated_annealing_maximum_temperature=2.5,
172  simulated_annealing_minimum_temperature_nframes=100,
173  simulated_annealing_maximum_temperature_nframes=100,
174  replica_exchange_minimum_temperature=1.0,
175  replica_exchange_maximum_temperature=2.5,
176  replica_exchange_swap=True,
177  num_sample_rounds=1,
178  number_of_best_scoring_models=500,
179  monte_carlo_steps=10,
180  self_adaptive=False,
181  molecular_dynamics_steps=10,
182  molecular_dynamics_max_time_step=1.0,
183  number_of_frames=1000,
184  save_coordinates_mode="lowest_temperature",
185  nframes_write_coordinates=1,
186  write_initial_rmf=True,
187  initial_rmf_name_suffix="initial",
188  stat_file_name_suffix="stat",
189  best_pdb_name_suffix="model",
190  mmcif=False,
191  do_clean_first=True,
192  do_create_directories=True,
193  global_output_directory="./",
194  rmf_dir="rmfs/",
195  best_pdb_dir="pdbs/",
196  replica_stat_file_suffix="stat_replica",
197  em_object_for_rmf=None,
198  atomistic=False,
199  replica_exchange_object=None,
200  test_mode=False,
201  score_moved=False,
202  use_nestor=False,
203  nestor_restraints=None,
204  nestor_rmf_fname_prefix="nested",
205  use_jax=False):
206  """Constructor.
207  @param model The IMP model
208  @param root_hier Top-level (System)hierarchy
209  @param monte_carlo_sample_objects Objects for MC sampling, which
210  should generally be a simple list of Mover objects, e.g.
211  from DegreesOfFreedom.get_movers().
212  @param molecular_dynamics_sample_objects Objects for MD sampling,
213  which should generally be a simple list of particles.
214  @param output_objects A list of structural objects and restraints
215  that will be included in output (ie, statistics "stat"
216  files). Any object that provides a get_output() method
217  can be used here. If None is passed
218  the macro will not write stat files.
219  @param rmf_output_objects A list of structural objects and
220  restraints that will be included in rmf. Any object
221  that provides a get_output() method can be used here.
222  @param monte_carlo_temperature MC temp (may need to be optimized
223  based on post-sampling analysis)
224  @param simulated_annealing If True, perform simulated annealing
225  @param simulated_annealing_minimum_temperature Should generally be
226  the same as monte_carlo_temperature.
227  @param simulated_annealing_minimum_temperature_nframes Number of
228  frames to compute at minimum temperature.
229  @param simulated_annealing_maximum_temperature_nframes Number of
230  frames to compute at
231  temps > simulated_annealing_maximum_temperature.
232  @param replica_exchange_minimum_temperature Low temp for REX; should
233  generally be the same as monte_carlo_temperature.
234  @param replica_exchange_maximum_temperature High temp for REX
235  @param replica_exchange_swap Boolean, enable disable temperature
236  swap (Default=True)
237  @param num_sample_rounds Number of rounds of MC/MD per cycle
238  @param number_of_best_scoring_models Number of top-scoring PDB/mmCIF
239  models to keep around for analysis.
240  @param mmcif If True, write best scoring models in mmCIF format;
241  if False (the default), write in legacy PDB format.
242  @param best_pdb_dir The directory under `global_output_directory`
243  where best-scoring PDB/mmCIF files are written.
244  @param best_pdb_name_suffix Part of the file name for best-scoring
245  PDB/mmCIF files.
246  @param monte_carlo_steps Number of MC steps per round
247  @param self_adaptive self adaptive scheme for Monte Carlo movers
248  @param molecular_dynamics_steps Number of MD steps per round
249  @param molecular_dynamics_max_time_step Max time step for MD
250  @param number_of_frames Number of REX frames to run
251  @param save_coordinates_mode string: how to save coordinates.
252  "lowest_temperature" (default) only the lowest temperatures
253  is saved
254  "25th_score" all replicas whose score is below the 25th
255  percentile
256  "50th_score" all replicas whose score is below the 50th
257  percentile
258  "75th_score" all replicas whose score is below the 75th
259  percentile
260  @param nframes_write_coordinates How often to write the coordinates
261  of a frame
262  @param write_initial_rmf Write the initial configuration
263  @param global_output_directory Folder that will be created to house
264  output.
265  @param test_mode Set to True to avoid writing any files, just test
266  one frame.
267  @param score_moved If True, attempt to speed up Monte Carlo
268  sampling by caching scoring function terms on particles
269  that didn't move.
270  @param use_nestor If True, follows the Nested Sampling workflow
271  of the NestOR module and skips writing stat files and
272  replica stat files.
273  @param nestor_restraints A list of restraints for which
274  likelihoods are to be computed for use by NestOR module.
275  @param nestor_rmf_fname_prefix Prefix to be used for storing .rmf3
276  files generated by NestOR .
277  @param use_jax If set to True, sample the scoring function using
278  JAX instead of IMP's internal C++ implementation (requires
279  that all PMI restraints used have a JAX implementation).
280  """
281  self.model = model
282  self.vars = {}
283  self._restart = None
284  self._restart_from_frame = 0
285 
286  # add check hierarchy is multistate
287  if output_objects == []:
288  # The "[]" in the default parameters is a global object, so make
289  # our own copy here
290  self.output_objects = []
291  else:
292  self.output_objects = output_objects
293  self.rmf_output_objects = rmf_output_objects
294  if (isinstance(root_hier, IMP.atom.Hierarchy)
295  and not root_hier.get_parent()):
296  if self.output_objects is not None:
297  self.output_objects.append(
298  IMP.pmi.io.TotalScoreOutput(self.model))
299  if self.rmf_output_objects is not None:
300  self.rmf_output_objects.append(
301  IMP.pmi.io.TotalScoreOutput(self.model))
302  self.root_hier = root_hier
303  states = IMP.atom.get_by_type(root_hier, IMP.atom.STATE_TYPE)
304  self.vars["number_of_states"] = len(states)
305  if len(states) > 1:
306  self.root_hiers = states
307  self.is_multi_state = True
308  else:
309  self.root_hier = root_hier
310  self.is_multi_state = False
311  else:
312  raise TypeError("Must provide System hierarchy (root_hier)")
313 
314  self._rmf_restraints = _RMFRestraints(model, None)
315  self.em_object_for_rmf = em_object_for_rmf
316  self.monte_carlo_sample_objects = monte_carlo_sample_objects
317  self.vars["self_adaptive"] = self_adaptive
318  self.molecular_dynamics_sample_objects = \
319  molecular_dynamics_sample_objects
320  self.replica_exchange_object = replica_exchange_object
321  self.molecular_dynamics_max_time_step = \
322  molecular_dynamics_max_time_step
323  self.vars["monte_carlo_temperature"] = monte_carlo_temperature
324  self.vars["replica_exchange_minimum_temperature"] = \
325  replica_exchange_minimum_temperature
326  self.vars["replica_exchange_maximum_temperature"] = \
327  replica_exchange_maximum_temperature
328  self.vars["replica_exchange_swap"] = replica_exchange_swap
329  self.vars["simulated_annealing"] = simulated_annealing
330  self.vars["simulated_annealing_minimum_temperature"] = \
331  simulated_annealing_minimum_temperature
332  self.vars["simulated_annealing_maximum_temperature"] = \
333  simulated_annealing_maximum_temperature
334  self.vars["simulated_annealing_minimum_temperature_nframes"] = \
335  simulated_annealing_minimum_temperature_nframes
336  self.vars["simulated_annealing_maximum_temperature_nframes"] = \
337  simulated_annealing_maximum_temperature_nframes
338 
339  self.vars["num_sample_rounds"] = num_sample_rounds
340  self.vars[
341  "number_of_best_scoring_models"] = number_of_best_scoring_models
342  self.vars["monte_carlo_steps"] = monte_carlo_steps
343  self.vars["molecular_dynamics_steps"] = molecular_dynamics_steps
344  self.vars["number_of_frames"] = number_of_frames
345  if save_coordinates_mode not in ("lowest_temperature", "25th_score",
346  "50th_score", "75th_score"):
347  raise Exception("save_coordinates_mode has unrecognized value")
348  else:
349  self.vars["save_coordinates_mode"] = save_coordinates_mode
350  self.vars["nframes_write_coordinates"] = nframes_write_coordinates
351  self.vars["write_initial_rmf"] = write_initial_rmf
352  self.vars["initial_rmf_name_suffix"] = initial_rmf_name_suffix
353  self.vars["best_pdb_name_suffix"] = best_pdb_name_suffix
354  self.vars["mmcif"] = mmcif
355  self.vars["stat_file_name_suffix"] = stat_file_name_suffix
356  self.vars["do_clean_first"] = do_clean_first
357  self.vars["do_create_directories"] = do_create_directories
358  self.vars["global_output_directory"] = global_output_directory
359  self.vars["rmf_dir"] = rmf_dir
360  self.vars["best_pdb_dir"] = best_pdb_dir
361  self.vars["atomistic"] = atomistic
362  self.vars["replica_stat_file_suffix"] = replica_stat_file_suffix
363  self.vars["geometries"] = None
364  self.test_mode = test_mode
365  self.score_moved = score_moved
366  self.use_jax = use_jax
367  self.vars["use_nestor"] = self.nest = use_nestor
368  self.nestor_restraints = nestor_restraints
369  self.nestor_rmf_fname = nestor_rmf_fname_prefix
370 
371  def set_restart(self, frames, restart_dir="restart"):
372  """Enable a simulation to be restarted if it is interrupted.
373 
374  If enabled, restart files containing a complete description of
375  the IMP system are written periodically during the simulation,
376  one per replica. If the simulation is interrupted, it can be
377  restarted using the restart_replica_exchange function, which
378  reads these files. Files for the previous restart are also kept
379  (with a .prev.pck extension) in case the most recent restart
380  is corrupted.
381 
382  Restart files contain IMP internal state and so will probably
383  not work with a different version of IMP, or on a different
384  operating system. As with all Python pickles, these files may
385  contain executable Python code and so you should not use a
386  restart file from an untrusted source.
387 
388  @param frames How often a restart file should be written
389  (number of frames), or zero to not write restart files
390  @param restart_dir The directory under `global_output_directory`
391  where restart files are written.
392  """
393  if frames == 0:
394  self._restart = None
395  else:
396  self._restart = _RestartInfo(frames, restart_dir)
397 
398  def add_geometries(self, geometries):
399  if self.vars["geometries"] is None:
400  self.vars["geometries"] = list(geometries)
401  else:
402  self.vars["geometries"].extend(geometries)
403 
404  def show_info(self):
405  print("ReplicaExchange: it generates initial.*.rmf3, stat.*.out, "
406  "rmfs/*.rmf3 for each replica ")
407  print("--- it stores the best scoring pdb models in pdbs/")
408  print("--- the stat.*.out and rmfs/*.rmf3 are saved only at the "
409  "lowest temperature")
410  if self._restart and self._restart.restarted:
411  print("--- this is a restart of a failed simulation")
412  print("--- variables:")
413  for k, v in sorted(self.vars.items(), key=itemgetter(0)):
414  print("------", k.ljust(30), v)
415 
416  def get_replica_exchange_object(self):
417  return self.replica_exchange_object
418 
419  def _add_provenance(self, sampler_md, sampler_mc):
420  """Record details about the sampling in the IMP Hierarchies"""
421  iterations = 0
422  if sampler_md:
423  method = "Molecular Dynamics"
424  iterations += self.vars["molecular_dynamics_steps"]
425  if sampler_mc:
426  method = "Hybrid MD/MC" if sampler_md else "Monte Carlo"
427  iterations += self.vars["monte_carlo_steps"]
428  # If no sampling is actually done, no provenance to write
429  if iterations == 0 or self.vars["number_of_frames"] == 0:
430  return
431  iterations *= self.vars["num_sample_rounds"]
432 
433  pi = self.model.add_particle("sampling")
435  self.model, pi, method, self.vars["number_of_frames"],
436  iterations)
437  p.set_number_of_replicas(
438  self.replica_exchange_object.get_number_of_replicas())
439  IMP.pmi.tools._add_pmi_provenance(self.root_hier)
440  IMP.core.add_provenance(self.model, self.root_hier, p)
441 
442  def _setup_mc_sampler(self):
443  sampler_mc = IMP.pmi.samplers.MonteCarlo(
444  self.model, self.monte_carlo_sample_objects,
445  self.vars["monte_carlo_temperature"],
446  score_moved=self.score_moved,
447  start_frame=self._restart_from_frame)
448  if self.use_jax:
449  sampler_mc.set_use_jax(self.vars["monte_carlo_steps"])
450  if self.vars["simulated_annealing"]:
451  tmin = self.vars["simulated_annealing_minimum_temperature"]
452  tmax = self.vars["simulated_annealing_maximum_temperature"]
453  nfmin = self.vars[
454  "simulated_annealing_minimum_temperature_nframes"]
455  nfmax = self.vars[
456  "simulated_annealing_maximum_temperature_nframes"]
457  sampler_mc.set_simulated_annealing(tmin, tmax, nfmin, nfmax)
458  if self.vars["self_adaptive"]:
459  sampler_mc.set_self_adaptive(
460  isselfadaptive=self.vars["self_adaptive"])
461  return sampler_mc
462 
463  def _setup_md_sampler(self):
465  self.model, self.molecular_dynamics_sample_objects,
466  self.vars["monte_carlo_temperature"],
467  maximum_time_step=self.molecular_dynamics_max_time_step,
468  start_frame=self._restart_from_frame)
469  if self.use_jax:
470  sampler_md.set_use_jax(self.vars["molecular_dynamics_steps"])
471  if self.vars["simulated_annealing"]:
472  tmin = self.vars["simulated_annealing_minimum_temperature"]
473  tmax = self.vars["simulated_annealing_maximum_temperature"]
474  nfmin = self.vars[
475  "simulated_annealing_minimum_temperature_nframes"]
476  nfmax = self.vars[
477  "simulated_annealing_maximum_temperature_nframes"]
478  sampler_md.set_simulated_annealing(tmin, tmax, nfmin, nfmax)
479  return sampler_md
480 
481  def _get_jax_model(self, sampler_mc):
482  if self.use_jax:
483  return sampler_mc.get_jax_model()
484 
485  def execute_macro(self):
486  # Are we restarting a failed simulation?
487  restarted = self._restart.restarted if self._restart else False
488 
489  stat_file = _StatFile(self.output_objects, self.rmf_output_objects)
490  temp_index_factor = 100000.0
491  samplers = []
492  sampler_mc = None
493  sampler_md = None
494  if self.monte_carlo_sample_objects is not None:
495  print("Setting up MonteCarlo")
496  sampler_mc = self._setup_mc_sampler()
497  stat_file.append(sampler_mc)
498  samplers.append(sampler_mc)
499 
500  if self.molecular_dynamics_sample_objects is not None:
501  print("Setting up MolecularDynamics")
502  sampler_md = self._setup_md_sampler()
503  stat_file.append(sampler_md)
504  samplers.append(sampler_md)
505 
506 # -------------------------------------------------------------------------
507 
508  print("Setting up ReplicaExchange")
510  self.model, self.vars["replica_exchange_minimum_temperature"],
511  self.vars["replica_exchange_maximum_temperature"], samplers,
512  replica_exchange_object=self.replica_exchange_object)
513  self.replica_exchange_object = rex.rem
514  if restarted:
515  # Restore replica exchange stats from restart
516  rex.stats = self._rex_stats
517  del self._rex_stats
518 
519  myindex = rex.get_my_index()
520  stat_file.append(rex)
521  # must reset the minimum temperature due to the
522  # different binary length of rem.get_my_parameter double and python
523  # float
524  min_temp_index = int(min(rex.get_temperatures()) * temp_index_factor)
525 
526 # -------------------------------------------------------------------------
527 
528  globaldir = self.vars["global_output_directory"] + "/"
529  rmf_dir = globaldir + self.vars["rmf_dir"]
530  pdb_dir = globaldir + self.vars["best_pdb_dir"]
531 
532  if not self.test_mode and not self.nest:
533  if self.vars["do_clean_first"]:
534  pass
535 
536  if self.vars["do_create_directories"]:
537 
538  os.makedirs(globaldir, exist_ok=True)
539  os.makedirs(rmf_dir, exist_ok=True)
540  if not self.is_multi_state:
541  os.makedirs(pdb_dir, exist_ok=True)
542  else:
543  for n in range(self.vars["number_of_states"]):
544  os.makedirs(pdb_dir + "/" + str(n), exist_ok=True)
545 
546 # -------------------------------------------------------------------------
547 
548  stat_file.append(IMP.pmi.tools.Stopwatch())
549 
550  output = IMP.pmi.output.Output(atomistic=self.vars["atomistic"])
551 
552  if not self.nest:
553  print("Setting up stat file")
554  low_temp_stat_file = globaldir + \
555  self.vars["stat_file_name_suffix"] + "." + \
556  str(myindex) + ".out"
557 
558  # Ensure model is updated before saving init files
559  if not self.test_mode:
560  self.model.update()
561 
562  if not self.test_mode and not self.nest:
563  if stat_file.objects is not None:
564  output.init_stat2(low_temp_stat_file,
565  stat_file.objects,
566  extralabels=["rmf_file", "rmf_frame_index"],
567  jax_model=self._get_jax_model(sampler_mc),
568  append=restarted)
569  # todo: also truncate outputs from MD?
570  if restarted and sampler_mc:
571  nline = output._count_stat2_nframe(
572  low_temp_stat_file, 'MonteCarlo_Nframe',
573  self._restart_from_frame)
574  if nline is not None:
575  output._truncate_stat2_nline(low_temp_stat_file, nline)
576  else:
577  print("Stat file writing is disabled")
578 
579  if stat_file.rmf_objects is not None and not self.nest:
580  print("Stat info being written in the rmf file")
581 
582  if not self.test_mode and not self.nest:
583  print("Setting up replica stat file")
584  replica_stat_file = globaldir + \
585  self.vars["replica_stat_file_suffix"] + "." + \
586  str(myindex) + ".out"
587  if not self.test_mode:
588  output.init_stat2(replica_stat_file, [rex],
589  extralabels=["score"],
590  jax_model=self._get_jax_model(sampler_mc),
591  append=restarted)
592  if restarted:
593  output._truncate_stat2_nline(
594  replica_stat_file, self._restart_from_frame)
595 
596  print("Setting up best pdb files")
597  if not self.is_multi_state:
598  if self.vars["number_of_best_scoring_models"] > 0:
599  output.init_pdb_best_scoring(
600  pdb_dir + "/" + self.vars["best_pdb_name_suffix"],
601  self.root_hier,
602  self.vars["number_of_best_scoring_models"],
603  replica_exchange=True,
604  mmcif=self.vars['mmcif'],
605  best_score_file=globaldir + "best.scores.rex.py")
606  pdbext = ".0.cif" if self.vars['mmcif'] else ".0.pdb"
607  output.write_psf(
608  pdb_dir + "/" + "model.psf",
609  pdb_dir + "/" +
610  self.vars["best_pdb_name_suffix"] + pdbext)
611  else:
612  if self.vars["number_of_best_scoring_models"] > 0:
613  for n in range(self.vars["number_of_states"]):
614  output.init_pdb_best_scoring(
615  pdb_dir + "/" + str(n) + "/" +
616  self.vars["best_pdb_name_suffix"],
617  self.root_hiers[n],
618  self.vars["number_of_best_scoring_models"],
619  replica_exchange=True,
620  mmcif=self.vars['mmcif'],
621  best_score_file=globaldir + "best.scores.rex.py")
622  pdbext = ".0.cif" if self.vars['mmcif'] else ".0.pdb"
623  output.write_psf(
624  pdb_dir + "/" + str(n) + "/" + "model.psf",
625  pdb_dir + "/" + str(n) + "/" +
626  self.vars["best_pdb_name_suffix"] + pdbext)
627 # ---------------------------------------------
628 
629  if self.em_object_for_rmf is not None:
630  output_hierarchies = [
631  self.root_hier,
632  self.em_object_for_rmf.get_density_as_hierarchy(
633  )]
634  else:
635  output_hierarchies = [self.root_hier]
636 
637  if not self.test_mode and not self.nest and not restarted:
638  print("Setting up and writing initial rmf coordinate file")
639  init_suffix = globaldir + self.vars["initial_rmf_name_suffix"]
640  output.init_rmf(init_suffix + "." + str(myindex) + ".rmf3",
641  output_hierarchies,
642  listofobjects=stat_file.rmf_objects)
643  if self._rmf_restraints:
644  output.add_restraints_to_rmf(
645  init_suffix + "." + str(myindex) + ".rmf3",
646  self._rmf_restraints)
647  output.write_rmf(init_suffix + "." + str(myindex) + ".rmf3")
648  output.close_rmf(init_suffix + "." + str(myindex) + ".rmf3")
649 
650  if not self.test_mode:
651  mpivs = IMP.pmi.samplers.MPI_values(self.replica_exchange_object)
652  else:
653  mpivs = _MockMPIValues()
654 
655  self._add_provenance(sampler_md, sampler_mc)
656 
657  if not self.test_mode and not self.nest:
658  print("Setting up production rmf files")
659  if restarted:
660  rmfname = f"{rmf_dir}/{myindex}.rs{self._restart._number}.rmf3"
661  else:
662  rmfname = rmf_dir + "/" + str(myindex) + ".rmf3"
663  output.init_rmf(rmfname, output_hierarchies,
664  geometries=self.vars["geometries"],
665  listofobjects=stat_file.rmf_objects)
666 
667  if self._rmf_restraints:
668  output.add_restraints_to_rmf(rmfname, self._rmf_restraints)
669 
670  if not self.test_mode and self.nest:
671  print("Setting up NestOR rmf files")
672  nestor_rmf_fname = str(self.nestor_rmf_fname) + '_' + \
673  str(self.replica_exchange_object.get_my_index()) + '.rmf3'
674 
675  output.init_rmf(nestor_rmf_fname, output_hierarchies,
676  geometries=self.vars["geometries"],
677  listofobjects=stat_file.rmf_objects)
678 
679  ntimes_at_low_temp = 0
680 
681  if myindex == 0 and not self.nest:
682  self.show_info()
683  self.replica_exchange_object.set_was_used(True)
684  nframes = self.vars["number_of_frames"]
685  if self.test_mode:
686  nframes = 1
687 
688  sampled_likelihoods = []
689  for i in range(self._restart_from_frame, nframes):
690  if self._restart and i != self._restart_from_frame:
691  self._restart._write_frame(self, i, myindex, rex.stats)
692  if self.test_mode:
693  score = 0.
694  else:
695  score = None
696  for nr in range(self.vars["num_sample_rounds"]):
697  if sampler_md is not None:
698  score = sampler_md.optimize(
699  self.vars["molecular_dynamics_steps"])
700  if sampler_mc is not None:
701  score = sampler_mc.optimize(
702  self.vars["monte_carlo_steps"])
703  if score is None:
705  self.model).evaluate(False)
706  elif (IMP.get_check_level() >= IMP.USAGE_AND_INTERNAL
707  and not self.use_jax):
708  # Final score from samplers should match the current
709  # score of the Model
710  check_score = IMP.pmi.tools.get_restraint_set(
711  self.model).evaluate(False)
712  assert abs(score - check_score) < 1e-4
713  mpivs.set_value("score", score)
714  if not self.nest:
715  output.set_output_entry("score", score)
716 
717  my_temp_index = int(rex.get_my_temp() * temp_index_factor)
718 
719  if self.vars["save_coordinates_mode"] == "lowest_temperature":
720  save_frame = (min_temp_index == my_temp_index)
721  elif self.vars["save_coordinates_mode"] == "25th_score":
722  score_perc = mpivs.get_percentile("score")
723  save_frame = (score_perc*100.0 <= 25.0)
724  elif self.vars["save_coordinates_mode"] == "50th_score":
725  score_perc = mpivs.get_percentile("score")
726  save_frame = (score_perc*100.0 <= 50.0)
727  elif self.vars["save_coordinates_mode"] == "75th_score":
728  score_perc = mpivs.get_percentile("score")
729  save_frame = (score_perc*100.0 <= 75.0)
730 
731  # Ensure model is updated before saving output files
732  if save_frame and not self.test_mode:
733  self.model.update()
734 
735  if save_frame:
736  print("--- frame %s score %s " % (str(i), str(score)))
737 
738  if self.nest:
739  if math.isnan(score):
740  sampled_likelihoods.append(math.nan)
741  else:
742  likelihood_for_sample = 1
743  for rstrnt in self.nestor_restraints:
744  likelihood_for_sample *= rstrnt.get_likelihood()
745  sampled_likelihoods.append(likelihood_for_sample)
746  output.write_rmf(nestor_rmf_fname)
747 
748  if not self.test_mode and not self.nest:
749  if i % self.vars["nframes_write_coordinates"] == 0:
750  print('--- writing coordinates')
751  if self.vars["number_of_best_scoring_models"] > 0:
752  output.write_pdb_best_scoring(score)
753  output.write_rmf(rmfname)
754  output.set_output_entry("rmf_file", rmfname)
755  output.set_output_entry("rmf_frame_index",
756  ntimes_at_low_temp)
757  else:
758  output.set_output_entry("rmf_file", rmfname)
759  output.set_output_entry("rmf_frame_index", '-1')
760  if stat_file.objects is not None:
761  output.write_stat2(
762  low_temp_stat_file,
763  jax_model=self._get_jax_model(sampler_mc))
764  ntimes_at_low_temp += 1
765 
766  if not self.test_mode and not self.nest:
767  output.write_stat2(
768  replica_stat_file,
769  jax_model=self._get_jax_model(sampler_mc))
770  if self.vars["replica_exchange_swap"]:
771  rex.swap_temp(i, score)
772 
773  if self.nest and len(sampled_likelihoods) > 0:
774  with open("likelihoods_"
775  + str(self.replica_exchange_object.get_my_index()),
776  "wb") as lif:
777  pickle.dump(sampled_likelihoods, lif)
778 
779  output.close_rmf(nestor_rmf_fname)
780 
781  for p, state in IMP.pmi.tools._all_protocol_outputs(self.root_hier):
782  p.add_replica_exchange(state, self)
783 
784  if not self.test_mode and not self.nest:
785  print("closing production rmf files")
786  output.close_rmf(rmfname)
787 
788 
789 def restart_replica_exchange(restart_dir, prev=False):
790  """Continue a failed ReplicaExchange sampling run.
791 
792  @see ReplicaExchange.set_restart
793 
794  @param restart_dir The directory containing the restart file(s).
795  @param prev If True, use the previous restart
796  (e.g. `restart.0.prev.pck`) rather than the most recent
797  restart (e.g. `restart.0.pck`)
798  """
799  # Make sure that we are running MPI with the same number of replicas
800  # as the original run
801  try:
802  import IMP.mpi
804  nproc, myindex = r.get_number_of_replicas(), r.get_my_index()
805  except ImportError:
806  # Not running with MPI; assume just one replica
807  nproc, myindex = 1, 0
808 
809  ext = 'prev.pck' if prev else 'pck'
810  with open(f'{restart_dir}/restart.{myindex}.{ext}', 'rb') as fh:
811  mc = pickle.load(fh)
812  old_nproc = mc.get_number_of_replicas()
813  if old_nproc != nproc:
814  raise ValueError(
815  f"Mismatch trying to read restart files: the original run used "
816  f"{old_nproc} replicas and this run has {nproc}")
817  return mc.execute_macro()
818 
819 
821  """A macro to build a IMP::pmi::topology::System based on a
822  TopologyReader object.
823 
824  Easily create multi-state systems by calling this macro
825  repeatedly with different TopologyReader objects!
826  A useful function is get_molecules() which returns the PMI Molecules
827  grouped by state as a dictionary with key = (molecule name),
828  value = IMP.pmi.topology.Molecule
829  Quick multi-state system:
830  @code{.python}
831  model = IMP.Model()
832  reader1 = IMP.pmi.topology.TopologyReader(tfile1)
833  reader2 = IMP.pmi.topology.TopologyReader(tfile2)
834  bs = IMP.pmi.macros.BuildSystem(model)
835  bs.add_state(reader1)
836  bs.add_state(reader2)
837  bs.execute_macro() # build everything including degrees of freedom
838  IMP.atom.show_molecular_hierarchy(bs.get_hierarchy())
839  ### now you have a two state system, you add restraints etc
840  @endcode
841  @note The "domain name" entry of the topology reader is not used.
842  All molecules are set up by the component name, but split into rigid bodies
843  as requested.
844  """
845 
846  _alphabets = {'DNA': IMP.pmi.alphabets.dna,
847  'RNA': IMP.pmi.alphabets.rna}
848 
849  def __init__(self, model, sequence_connectivity_scale=4.0,
850  force_create_gmm_files=False, resolutions=[1, 10],
851  name='System'):
852  """Constructor
853  @param model An IMP Model
854  @param sequence_connectivity_scale For scaling the connectivity
855  restraint
856  @param force_create_gmm_files If True, will sample and create GMMs
857  no matter what. If False, will only sample if the
858  files don't exist. If number of Gaussians is zero, won't
859  do anything.
860  @param resolutions The resolutions to build for structured regions
861  @param name The name of the top-level hierarchy node.
862  """
863  self.model = model
864  self.system = IMP.pmi.topology.System(self.model, name=name)
865  self._readers = [] # the TopologyReaders (one per state)
866  # TempResidues for each domain key=unique name,
867  # value=(atomic_res,non_atomic_res).
868  self._domain_res = []
869  self._domains = [] # key = domain unique name, value = Component
870  self.force_create_gmm_files = force_create_gmm_files
871  self.resolutions = resolutions
872 
873  def add_state(self, reader, keep_chain_id=False, fasta_name_map=None,
874  chain_ids=None):
875  """Add a state using the topology info in a
876  IMP::pmi::topology::TopologyReader object.
877  When you are done adding states, call execute_macro()
878  @param reader The TopologyReader object
879  @param keep_chain_id If True, keep the chain IDs from the
880  original PDB files, if available
881  @param fasta_name_map dictionary for converting protein names
882  found in the fasta file
883  @param chain_ids A list or string of chain IDs for assigning to
884  newly-created molecules, e.g.
885  `string.ascii_uppercase+string.ascii_lowercase+string.digits`.
886  If not specified, chain IDs A through Z are assigned, then
887  AA through AZ, then BA through BZ, and so on, in the same
888  fashion as PDB.
889  """
890  state = self.system.create_state()
891  self._readers.append(reader)
892  # key is unique name, value is (atomic res, nonatomicres)
893  these_domain_res = {}
894  these_domains = {} # key is unique name, value is _Component
895  if chain_ids is None:
896  chain_ids = IMP.pmi.output._ChainIDs()
897  numchain = 0
898 
899  # setup representation
900  # loop over molecules, copies, then domains
901  for molname in reader.get_molecules():
902  copies = reader.get_molecules()[molname].domains
903  for nc, copyname in enumerate(copies):
904  print("BuildSystem.add_state: setting up molecule %s copy "
905  "number %s" % (molname, str(nc)))
906  copy = copies[copyname]
907  # option to not rename chains
908  if keep_chain_id:
909  all_chains = [c for c in copy if c.chain is not None]
910  if all_chains:
911  chain_id = all_chains[0].chain
912  else:
913  chain_id = chain_ids[numchain]
914  warnings.warn(
915  "No PDBs specified for %s, so keep_chain_id has "
916  "no effect; using default chain ID '%s'"
917  % (molname, chain_id), IMP.pmi.ParameterWarning)
918  else:
919  chain_id = chain_ids[numchain]
920  if nc == 0:
921  alphabet = IMP.pmi.alphabets.amino_acid
922  fasta_flag = copy[0].fasta_flag
923  if fasta_flag in self._alphabets:
924  alphabet = self._alphabets[fasta_flag]
926  copy[0].fasta_file, fasta_name_map)
927  seq = seqs[copy[0].fasta_id]
928  print("BuildSystem.add_state: molecule %s sequence has "
929  "%s residues" % (molname, len(seq)))
930  orig_mol = state.create_molecule(
931  molname, seq, chain_id, alphabet=alphabet,
932  uniprot=seqs.uniprot.get(copy[0].fasta_id))
933  mol = orig_mol
934  numchain += 1
935  else:
936  print("BuildSystem.add_state: creating a copy for "
937  "molecule %s" % molname)
938  mol = orig_mol.create_copy(chain_id)
939  numchain += 1
940 
941  for domainnumber, domain in enumerate(copy):
942  print("BuildSystem.add_state: ---- setting up domain %s "
943  "of molecule %s" % (domainnumber, molname))
944  # we build everything in the residue range, even if it
945  # extends beyond what's in the actual PDB file
946  these_domains[domain.get_unique_name()] = domain
947  if domain.residue_range == [] or \
948  domain.residue_range is None:
949  domain_res = mol.get_residues()
950  else:
951  start = domain.residue_range[0]+domain.pdb_offset
952  if domain.residue_range[1] == 'END':
953  end = len(mol.sequence)
954  else:
955  end = domain.residue_range[1]+domain.pdb_offset
956  domain_res = mol.residue_range(start-1, end-1)
957  print("BuildSystem.add_state: -------- domain %s of "
958  "molecule %s extends from residue %s to "
959  "residue %s "
960  % (domainnumber, molname, start, end))
961  if domain.pdb_file == "BEADS":
962  print("BuildSystem.add_state: -------- domain %s of "
963  "molecule %s represented by BEADS "
964  % (domainnumber, molname))
965  mol.add_representation(
966  domain_res,
967  resolutions=[domain.bead_size],
968  setup_particles_as_densities=(
969  domain.em_residues_per_gaussian != 0),
970  color=domain.color)
971  these_domain_res[domain.get_unique_name()] = \
972  (set(), domain_res)
973  elif domain.pdb_file == "IDEAL_HELIX":
974  print("BuildSystem.add_state: -------- domain %s of "
975  "molecule %s represented by IDEAL_HELIX "
976  % (domainnumber, molname))
977  emper = domain.em_residues_per_gaussian
978  mol.add_representation(
979  domain_res,
980  resolutions=self.resolutions,
981  ideal_helix=True,
982  density_residues_per_component=emper,
983  density_prefix=domain.density_prefix,
984  density_force_compute=self.force_create_gmm_files,
985  color=domain.color)
986  these_domain_res[domain.get_unique_name()] = \
987  (domain_res, set())
988  else:
989  print("BuildSystem.add_state: -------- domain %s of "
990  "molecule %s represented by pdb file %s "
991  % (domainnumber, molname, domain.pdb_file))
992  domain_atomic = mol.add_structure(domain.pdb_file,
993  domain.chain,
994  domain.residue_range,
995  domain.pdb_offset,
996  soft_check=True)
997  domain_non_atomic = domain_res - domain_atomic
998  if not domain.em_residues_per_gaussian:
999  mol.add_representation(
1000  domain_atomic, resolutions=self.resolutions,
1001  color=domain.color)
1002  if len(domain_non_atomic) > 0:
1003  mol.add_representation(
1004  domain_non_atomic,
1005  resolutions=[domain.bead_size],
1006  color=domain.color)
1007  else:
1008  print("BuildSystem.add_state: -------- domain %s "
1009  "of molecule %s represented by gaussians "
1010  % (domainnumber, molname))
1011  emper = domain.em_residues_per_gaussian
1012  creategmm = self.force_create_gmm_files
1013  mol.add_representation(
1014  domain_atomic,
1015  resolutions=self.resolutions,
1016  density_residues_per_component=emper,
1017  density_prefix=domain.density_prefix,
1018  density_force_compute=creategmm,
1019  color=domain.color)
1020  if len(domain_non_atomic) > 0:
1021  mol.add_representation(
1022  domain_non_atomic,
1023  resolutions=[domain.bead_size],
1024  setup_particles_as_densities=True,
1025  color=domain.color)
1026  these_domain_res[domain.get_unique_name()] = (
1027  domain_atomic, domain_non_atomic)
1028  self._domain_res.append(these_domain_res)
1029  self._domains.append(these_domains)
1030  print('BuildSystem.add_state: State', len(self.system.states), 'added')
1031  return state
1032 
1033  def get_molecules(self):
1034  """Return list of all molecules grouped by state.
1035  For each state, it's a dictionary of Molecules where key is the
1036  molecule name
1037  """
1038  return [s.get_molecules() for s in self.system.get_states()]
1039 
1040  def get_molecule(self, molname, copy_index=0, state_index=0):
1041  return self.system.get_states()[state_index].get_molecules()[
1042  molname][copy_index]
1043 
1044  def execute_macro(self, max_rb_trans=4.0, max_rb_rot=0.04,
1045  max_bead_trans=4.0, max_srb_trans=4.0, max_srb_rot=0.04):
1046  """Builds representations and sets up degrees of freedom"""
1047  print("BuildSystem.execute_macro: building representations")
1048  self.root_hier = self.system.build()
1049 
1050  print("BuildSystem.execute_macro: setting up degrees of freedom")
1051  self.dof = IMP.pmi.dof.DegreesOfFreedom(self.model)
1052  for nstate, reader in enumerate(self._readers):
1053  rbs = reader.get_rigid_bodies()
1054  srbs = reader.get_super_rigid_bodies()
1055  csrbs = reader.get_chains_of_super_rigid_bodies()
1056 
1057  # add rigid bodies
1058  domains_in_rbs = set()
1059  for rblist in rbs:
1060  print("BuildSystem.execute_macro: -------- building rigid "
1061  "body %s" % (str(rblist)))
1062  all_res = IMP.pmi.tools.OrderedSet()
1063  bead_res = IMP.pmi.tools.OrderedSet()
1064  for dname in rblist:
1065  domain = self._domains[nstate][dname]
1066  print("BuildSystem.execute_macro: -------- adding %s"
1067  % (str(dname)))
1068  all_res |= self._domain_res[nstate][dname][0]
1069  bead_res |= self._domain_res[nstate][dname][1]
1070  domains_in_rbs.add(dname)
1071  all_res |= bead_res
1072  print("BuildSystem.execute_macro: -------- creating rigid "
1073  "body with max_trans %s max_rot %s "
1074  "non_rigid_max_trans %s"
1075  % (str(max_rb_trans), str(max_rb_rot),
1076  str(max_bead_trans)))
1077  self.dof.create_rigid_body(all_res,
1078  nonrigid_parts=bead_res,
1079  max_trans=max_rb_trans,
1080  max_rot=max_rb_rot,
1081  nonrigid_max_trans=max_bead_trans,
1082  name="RigidBody %s" % dname)
1083 
1084  # if you have any domains not in an RB, set them as flexible beads
1085  for dname, domain in self._domains[nstate].items():
1086  if dname not in domains_in_rbs:
1087  if domain.pdb_file != "BEADS":
1088  warnings.warn(
1089  "No rigid bodies set for %s. Residues read from "
1090  "the PDB file will not be sampled - only regions "
1091  "missing from the PDB will be treated flexibly. "
1092  "To sample the entire sequence, use BEADS instead "
1093  "of a PDB file name" % dname,
1095  self.dof.create_flexible_beads(
1096  self._domain_res[nstate][dname][1],
1097  max_trans=max_bead_trans)
1098 
1099  # add super rigid bodies
1100  for srblist in srbs:
1101  print("BuildSystem.execute_macro: -------- building "
1102  "super rigid body %s" % (str(srblist)))
1103  all_res = IMP.pmi.tools.OrderedSet()
1104  for dname in srblist:
1105  print("BuildSystem.execute_macro: -------- adding %s"
1106  % (str(dname)))
1107  all_res |= self._domain_res[nstate][dname][0]
1108  all_res |= self._domain_res[nstate][dname][1]
1109 
1110  print("BuildSystem.execute_macro: -------- creating super "
1111  "rigid body with max_trans %s max_rot %s "
1112  % (str(max_srb_trans), str(max_srb_rot)))
1113  self.dof.create_super_rigid_body(
1114  all_res, max_trans=max_srb_trans, max_rot=max_srb_rot)
1115 
1116  # add chains of super rigid bodies
1117  for csrblist in csrbs:
1118  all_res = IMP.pmi.tools.OrderedSet()
1119  for dname in csrblist:
1120  all_res |= self._domain_res[nstate][dname][0]
1121  all_res |= self._domain_res[nstate][dname][1]
1122  all_res = list(all_res)
1123  all_res.sort(key=lambda r: r.get_index())
1124  self.dof.create_main_chain_mover(all_res)
1125  return self.root_hier, self.dof
1126 
1127 
1128 @IMP.deprecated_object("2.8", "Use AnalysisReplicaExchange instead")
1130  """A macro for running all the basic operations of analysis.
1131  Includes clustering, precision analysis, and making ensemble density maps.
1132  A number of plots are also supported.
1133  """
1134  def __init__(self, model,
1135  merge_directories=["./"],
1136  stat_file_name_suffix="stat",
1137  best_pdb_name_suffix="model",
1138  do_clean_first=True,
1139  do_create_directories=True,
1140  global_output_directory="output/",
1141  replica_stat_file_suffix="stat_replica",
1142  global_analysis_result_directory="./analysis/",
1143  test_mode=False):
1144  """Constructor.
1145  @param model The IMP model
1146  @param stat_file_name_suffix
1147  @param merge_directories The directories containing output files
1148  @param best_pdb_name_suffix
1149  @param do_clean_first
1150  @param do_create_directories
1151  @param global_output_directory Where everything is
1152  @param replica_stat_file_suffix
1153  @param global_analysis_result_directory
1154  @param test_mode If True, nothing is changed on disk
1155  """
1156 
1157  try:
1158  from mpi4py import MPI
1159  self.comm = MPI.COMM_WORLD
1160  self.rank = self.comm.Get_rank()
1161  self.number_of_processes = self.comm.size
1162  except ImportError:
1163  self.rank = 0
1164  self.number_of_processes = 1
1165 
1166  self.test_mode = test_mode
1167  self._protocol_output = []
1168  self.cluster_obj = None
1169  self.model = model
1170  stat_dir = global_output_directory
1171  self.stat_files = []
1172  # it contains the position of the root directories
1173  for rd in merge_directories:
1174  stat_files = glob.glob(os.path.join(rd, stat_dir, "stat.*.out"))
1175  if len(stat_files) == 0:
1176  warnings.warn("no stat files found in %s"
1177  % os.path.join(rd, stat_dir),
1179  self.stat_files += stat_files
1180 
1181  def add_protocol_output(self, p):
1182  """Capture details of the modeling protocol.
1183  @param p an instance of IMP.pmi.output.ProtocolOutput or a subclass.
1184  """
1185  # Assume last state is the one we're interested in
1186  self._protocol_output.append((p, p._last_state))
1187 
1188  def get_modeling_trajectory(self,
1189  score_key="Total_Score",
1190  rmf_file_key="rmf_file",
1191  rmf_file_frame_key="rmf_frame_index",
1192  outputdir="./",
1193  get_every=1,
1194  nframes_trajectory=10000):
1195  """ Get a trajectory of the modeling run, for generating
1196  demonstrative movies
1197 
1198  @param score_key The score for ranking models
1199  @param rmf_file_key Key pointing to RMF filename
1200  @param rmf_file_frame_key Key pointing to RMF frame number
1201  @param outputdir The local output directory used in the run
1202  @param get_every Extract every nth frame
1203  @param nframes_trajectory Total number of frames of the trajectory
1204  """
1205  import math
1206 
1207  trajectory_models = IMP.pmi.io.get_trajectory_models(
1208  self.stat_files, score_key, rmf_file_key, rmf_file_frame_key,
1209  get_every)
1210  score_list = list(map(float, trajectory_models[2]))
1211 
1212  max_score = max(score_list)
1213  min_score = min(score_list)
1214 
1215  bins = [(max_score-min_score)*math.exp(-float(i))+min_score
1216  for i in range(nframes_trajectory)]
1217  binned_scores = [None]*nframes_trajectory
1218  binned_model_indexes = [-1]*nframes_trajectory
1219 
1220  for model_index, s in enumerate(score_list):
1221  bins_score_diffs = [abs(s-b) for b in bins]
1222  bin_index = min(enumerate(bins_score_diffs), key=itemgetter(1))[0]
1223  if binned_scores[bin_index] is None:
1224  binned_scores[bin_index] = s
1225  binned_model_indexes[bin_index] = model_index
1226  else:
1227  old_diff = abs(binned_scores[bin_index]-bins[bin_index])
1228  new_diff = abs(s-bins[bin_index])
1229  if new_diff < old_diff:
1230  binned_scores[bin_index] = s
1231  binned_model_indexes[bin_index] = model_index
1232 
1233  print(binned_scores)
1234  print(binned_model_indexes)
1235 
1236  def _expand_ambiguity(self, prot, d):
1237  """If using PMI2, expand the dictionary to include copies as
1238  ambiguous options
1239 
1240  This also keeps the states separate.
1241  """
1242  newdict = {}
1243  for key in d:
1244  val = d[key]
1245  if '..' in key or (isinstance(val, tuple) and len(val) >= 3):
1246  newdict[key] = val
1247  continue
1248  states = IMP.atom.get_by_type(prot, IMP.atom.STATE_TYPE)
1249  if isinstance(val, tuple):
1250  start = val[0]
1251  stop = val[1]
1252  name = val[2]
1253  else:
1254  start = 1
1255  stop = -1
1256  name = val
1257  for nst in range(len(states)):
1258  sel = IMP.atom.Selection(prot, molecule=name, state_index=nst)
1259  copies = sel.get_selected_particles(with_representation=False)
1260  if len(copies) > 1:
1261  for nc in range(len(copies)):
1262  if len(states) > 1:
1263  newdict['%s.%i..%i' % (name, nst, nc)] = \
1264  (start, stop, name, nc, nst)
1265  else:
1266  newdict['%s..%i' % (name, nc)] = \
1267  (start, stop, name, nc, nst)
1268  else:
1269  newdict[key] = val
1270  return newdict
1271 
1272  def clustering(self,
1273  score_key="Total_Score",
1274  rmf_file_key="rmf_file",
1275  rmf_file_frame_key="rmf_frame_index",
1276  state_number=0,
1277  prefiltervalue=None,
1278  feature_keys=[],
1279  outputdir="./",
1280  alignment_components=None,
1281  number_of_best_scoring_models=10,
1282  rmsd_calculation_components=None,
1283  distance_matrix_file='distances.mat',
1284  load_distance_matrix_file=False,
1285  skip_clustering=False,
1286  number_of_clusters=1,
1287  display_plot=False,
1288  exit_after_display=True,
1289  get_every=1,
1290  first_and_last_frames=None,
1291  density_custom_ranges=None,
1292  write_pdb_with_centered_coordinates=False,
1293  voxel_size=5.0):
1294  """Get the best scoring models, compute a distance matrix,
1295  cluster them, and create density maps.
1296 
1297  Tuple format: "molname" just the molecule,
1298  or (start,stop,molname,copy_num(optional),state_num(optional)
1299  Can pass None for copy or state to ignore that field.
1300  If you don't pass a specific copy number
1301 
1302  @param score_key The score for ranking models.
1303  @param rmf_file_key Key pointing to RMF filename
1304  @param rmf_file_frame_key Key pointing to RMF frame number
1305  @param state_number State number to analyze
1306  @param prefiltervalue Only include frames where the
1307  score key is below this value
1308  @param feature_keys Keywords for which you want to
1309  calculate average, medians, etc.
1310  If you pass "Keyname" it'll include everything that matches
1311  "*Keyname*"
1312  @param outputdir The local output directory used in
1313  the run
1314  @param alignment_components Dictionary with keys=groupname,
1315  values are tuples for aligning the structures
1316  e.g. {"Rpb1": (20,100,"Rpb1"),"Rpb2":"Rpb2"}
1317  @param number_of_best_scoring_models Num models to keep per run
1318  @param rmsd_calculation_components For calculating RMSD
1319  (same format as alignment_components)
1320  @param distance_matrix_file Where to store/read the
1321  distance matrix
1322  @param load_distance_matrix_file Try to load the distance
1323  matrix file
1324  @param skip_clustering Just extract the best scoring
1325  models and save the pdbs
1326  @param number_of_clusters Number of k-means clusters
1327  @param display_plot Display the distance matrix
1328  @param exit_after_display Exit after displaying distance
1329  matrix
1330  @param get_every Extract every nth frame
1331  @param first_and_last_frames A tuple with the first and last
1332  frames to be analyzed. Values are percentages!
1333  Default: get all frames
1334  @param density_custom_ranges For density calculation
1335  (same format as alignment_components)
1336  @param write_pdb_with_centered_coordinates
1337  @param voxel_size Used for the density output
1338  """
1339  # Track provenance information to be added to each output model
1340  prov = []
1341  self._outputdir = Path(outputdir).absolute()
1342  self._number_of_clusters = number_of_clusters
1343  for p, state in self._protocol_output:
1344  p.add_replica_exchange_analysis(state, self, density_custom_ranges)
1345 
1346  if self.test_mode:
1347  return
1348 
1349  if self.rank == 0:
1350  try:
1351  os.mkdir(outputdir)
1352  except: # noqa: E722
1353  pass
1354 
1355  if not load_distance_matrix_file:
1356  if len(self.stat_files) == 0:
1357  print("ERROR: no stat file found in the given path")
1358  return
1359  my_stat_files = IMP.pmi.tools.chunk_list_into_segments(
1360  self.stat_files, self.number_of_processes)[self.rank]
1361 
1362  # read ahead to check if you need the PMI2 score key instead
1363  for k in (score_key, rmf_file_key, rmf_file_frame_key):
1364  if k in feature_keys:
1365  warnings.warn(
1366  "no need to pass " + k + " to feature_keys.",
1368  feature_keys.remove(k)
1369 
1370  best_models = IMP.pmi.io.get_best_models(
1371  my_stat_files, score_key, feature_keys, rmf_file_key,
1372  rmf_file_frame_key, prefiltervalue, get_every, provenance=prov)
1373  rmf_file_list = best_models[0]
1374  rmf_file_frame_list = best_models[1]
1375  score_list = best_models[2]
1376  feature_keyword_list_dict = best_models[3]
1377 
1378 # ------------------------------------------------------------------------
1379 # collect all the files and scores
1380 # ------------------------------------------------------------------------
1381 
1382  if self.number_of_processes > 1:
1383  score_list = IMP.pmi.tools.scatter_and_gather(score_list)
1384  rmf_file_list = IMP.pmi.tools.scatter_and_gather(rmf_file_list)
1385  rmf_file_frame_list = IMP.pmi.tools.scatter_and_gather(
1386  rmf_file_frame_list)
1387  for k in feature_keyword_list_dict:
1388  feature_keyword_list_dict[k] = \
1390  feature_keyword_list_dict[k])
1391 
1392  # sort by score and get the best scoring ones
1393  score_rmf_tuples = list(zip(score_list,
1394  rmf_file_list,
1395  rmf_file_frame_list,
1396  list(range(len(score_list)))))
1397 
1398  if density_custom_ranges:
1399  for k in density_custom_ranges:
1400  if not isinstance(density_custom_ranges[k], list):
1401  raise Exception("Density custom ranges: values must "
1402  "be lists of tuples")
1403 
1404  # keep subset of frames if requested
1405  if first_and_last_frames is not None:
1406  nframes = len(score_rmf_tuples)
1407  first_frame = int(first_and_last_frames[0] * nframes)
1408  last_frame = int(first_and_last_frames[1] * nframes)
1409  if last_frame > len(score_rmf_tuples):
1410  last_frame = -1
1411  score_rmf_tuples = score_rmf_tuples[first_frame:last_frame]
1412 
1413  # sort RMFs by the score_key in ascending order, and store the rank
1414  best_score_rmf_tuples = sorted(
1415  score_rmf_tuples,
1416  key=lambda x: float(x[0]))[:number_of_best_scoring_models]
1417  best_score_rmf_tuples = [t+(n,) for n, t in
1418  enumerate(best_score_rmf_tuples)]
1419  # Note in the provenance info that we only kept best-scoring models
1420  prov.append(IMP.pmi.io.FilterProvenance(
1421  "Best scoring", 0, number_of_best_scoring_models))
1422  # sort the feature scores in the same way
1423  best_score_feature_keyword_list_dict = defaultdict(list)
1424  for tpl in best_score_rmf_tuples:
1425  index = tpl[3]
1426  for f in feature_keyword_list_dict:
1427  best_score_feature_keyword_list_dict[f].append(
1428  feature_keyword_list_dict[f][index])
1429  my_best_score_rmf_tuples = IMP.pmi.tools.chunk_list_into_segments(
1430  best_score_rmf_tuples,
1431  self.number_of_processes)[self.rank]
1432 
1433  # expand the dictionaries to include ambiguous copies
1434  prot_ahead = IMP.pmi.analysis.get_hiers_from_rmf(
1435  self.model, 0, my_best_score_rmf_tuples[0][1])[0]
1436  if rmsd_calculation_components is not None:
1437  tmp = self._expand_ambiguity(
1438  prot_ahead, rmsd_calculation_components)
1439  if tmp != rmsd_calculation_components:
1440  print('Detected ambiguity, expand rmsd components to',
1441  tmp)
1442  rmsd_calculation_components = tmp
1443  if alignment_components is not None:
1444  tmp = self._expand_ambiguity(prot_ahead,
1445  alignment_components)
1446  if tmp != alignment_components:
1447  print('Detected ambiguity, expand alignment '
1448  'components to', tmp)
1449  alignment_components = tmp
1450 
1451 # -------------------------------------------------------------
1452 # read the coordinates
1453 # ------------------------------------------------------------
1454  rmsd_weights = IMP.pmi.io.get_bead_sizes(
1455  self.model, my_best_score_rmf_tuples[0],
1456  rmsd_calculation_components, state_number=state_number)
1458  self.model, my_best_score_rmf_tuples, alignment_components,
1459  rmsd_calculation_components, state_number=state_number)
1460 
1461  # note! the coordinates are simply float tuples, NOT decorators,
1462  # NOT Vector3D, NOR particles, because these object cannot be
1463  # serialized. We need serialization
1464  # for the parallel computation based on mpi.
1465 
1466  # dict:key=component name,val=coords per hit
1467  all_coordinates = got_coords[0]
1468 
1469  # same as above, limited to alignment bits
1470  alignment_coordinates = got_coords[1]
1471 
1472  # same as above, limited to RMSD bits
1473  rmsd_coordinates = got_coords[2]
1474 
1475  # dictionary with key=RMF, value=score rank
1476  rmf_file_name_index_dict = got_coords[3]
1477 
1478  # RMF file per hit
1479  all_rmf_file_names = got_coords[4]
1480 
1481 # ------------------------------------------------------------------------
1482 # optionally don't compute distance matrix or cluster, just write top files
1483 # ------------------------------------------------------------------------
1484  if skip_clustering:
1485  if density_custom_ranges:
1486  DensModule = IMP.pmi.analysis.GetModelDensity(
1487  density_custom_ranges, voxel=voxel_size)
1488 
1489  dircluster = os.path.join(outputdir,
1490  "all_models."+str(self.rank))
1491  try:
1492  os.mkdir(outputdir)
1493  except: # noqa: E722
1494  pass
1495  try:
1496  os.mkdir(dircluster)
1497  except: # noqa: E722
1498  pass
1499  clusstat = open(os.path.join(
1500  dircluster, "stat."+str(self.rank)+".out"), "w")
1501  for cnt, tpl in enumerate(my_best_score_rmf_tuples):
1502  rmf_name = tpl[1]
1503  rmf_frame_number = tpl[2]
1504  tmp_dict = {}
1505  index = tpl[4]
1506  for key in best_score_feature_keyword_list_dict:
1507  tmp_dict[key] = \
1508  best_score_feature_keyword_list_dict[key][index]
1509 
1510  if cnt == 0:
1511  prots, rs = \
1512  IMP.pmi.analysis.get_hiers_and_restraints_from_rmf(
1513  self.model, rmf_frame_number, rmf_name)
1514  else:
1515  linking_successful = \
1516  IMP.pmi.analysis.link_hiers_and_restraints_to_rmf(
1517  self.model, prots, rs, rmf_frame_number,
1518  rmf_name)
1519  if not linking_successful:
1520  continue
1521 
1522  if not prots:
1523  continue
1524 
1525  states = IMP.atom.get_by_type(
1526  prots[0], IMP.atom.STATE_TYPE)
1527  prot = states[state_number]
1528 
1529  # get transformation aligning coordinates of
1530  # requested tuples to the first RMF file
1531  if cnt == 0:
1532  coords_f1 = alignment_coordinates[cnt]
1533  if cnt > 0:
1534  coords_f2 = alignment_coordinates[cnt]
1535  if coords_f2:
1537  coords_f1, coords_f2)
1538  transformation = Ali.align()[1]
1539  else:
1540  transformation = \
1542 
1543  rbs = set()
1544  for p in IMP.atom.get_leaves(prot):
1545  if not IMP.core.XYZR.get_is_setup(p):
1547  IMP.core.XYZR(p).set_radius(0.0001)
1548  IMP.core.XYZR(p).set_coordinates((0, 0, 0))
1549 
1551  rbm = IMP.core.RigidBodyMember(p)
1552  rb = rbm.get_rigid_body()
1553  rbs.add(rb)
1554  else:
1556  transformation)
1557  for rb in rbs:
1558  IMP.core.transform(rb, transformation)
1559 
1560  o = IMP.pmi.output.Output()
1561  self.model.update()
1562  out_pdb_fn = os.path.join(
1563  dircluster, str(cnt)+"."+str(self.rank)+".pdb")
1564  out_rmf_fn = os.path.join(
1565  dircluster, str(cnt)+"."+str(self.rank)+".rmf3")
1566  o.init_pdb(out_pdb_fn, prot)
1567  tc = write_pdb_with_centered_coordinates
1568  o.write_pdb(out_pdb_fn,
1569  translate_to_geometric_center=tc)
1570 
1571  tmp_dict["local_pdb_file_name"] = \
1572  os.path.basename(out_pdb_fn)
1573  tmp_dict["rmf_file_full_path"] = rmf_name
1574  tmp_dict["local_rmf_file_name"] = \
1575  os.path.basename(out_rmf_fn)
1576  tmp_dict["local_rmf_frame_number"] = 0
1577 
1578  clusstat.write(str(tmp_dict)+"\n")
1579 
1580  # create a single-state System and write that
1582  IMP.Particle(self.model))
1583  h.set_name("System")
1584  h.add_child(prot)
1585  o.init_rmf(out_rmf_fn, [h], rs)
1586 
1587  o.write_rmf(out_rmf_fn)
1588  o.close_rmf(out_rmf_fn)
1589  # add the density
1590  if density_custom_ranges:
1591  DensModule.add_subunits_density(prot)
1592 
1593  if density_custom_ranges:
1594  DensModule.write_mrc(path=dircluster)
1595  del DensModule
1596  return
1597 
1598  # broadcast the coordinates
1599  if self.number_of_processes > 1:
1600  all_coordinates = IMP.pmi.tools.scatter_and_gather(
1601  all_coordinates)
1602  all_rmf_file_names = IMP.pmi.tools.scatter_and_gather(
1603  all_rmf_file_names)
1604  rmf_file_name_index_dict = IMP.pmi.tools.scatter_and_gather(
1605  rmf_file_name_index_dict)
1606  alignment_coordinates = IMP.pmi.tools.scatter_and_gather(
1607  alignment_coordinates)
1608  rmsd_coordinates = IMP.pmi.tools.scatter_and_gather(
1609  rmsd_coordinates)
1610 
1611  if self.rank == 0:
1612  # save needed information in external files
1613  self.save_objects(
1614  [best_score_feature_keyword_list_dict,
1615  rmf_file_name_index_dict],
1616  ".macro.pkl")
1617 
1618 # ------------------------------------------------------------------------
1619 # Calculate distance matrix and cluster
1620 # ------------------------------------------------------------------------
1621  print("setup clustering class")
1622  self.cluster_obj = IMP.pmi.analysis.Clustering(rmsd_weights)
1623 
1624  for n, model_coordinate_dict in enumerate(all_coordinates):
1625  # let's try to align
1626  if (alignment_components is not None
1627  and len(self.cluster_obj.all_coords) == 0):
1628  # set the first model as template coordinates
1629  self.cluster_obj.set_template(alignment_coordinates[n])
1630  self.cluster_obj.fill(all_rmf_file_names[n],
1631  rmsd_coordinates[n])
1632  print("Global calculating the distance matrix")
1633 
1634  # calculate distance matrix, all against all
1635  self.cluster_obj.dist_matrix()
1636 
1637  # perform clustering and optionally display
1638  if self.rank == 0:
1639  self.cluster_obj.do_cluster(number_of_clusters)
1640  if display_plot:
1641  if self.rank == 0:
1642  self.cluster_obj.plot_matrix(
1643  figurename=os.path.join(outputdir,
1644  'dist_matrix.pdf'))
1645  if exit_after_display:
1646  exit()
1647  self.cluster_obj.save_distance_matrix_file(
1648  file_name=distance_matrix_file)
1649 
1650 # ------------------------------------------------------------------------
1651 # Alternatively, load the distance matrix from file and cluster that
1652 # ------------------------------------------------------------------------
1653  else:
1654  if self.rank == 0:
1655  print("setup clustering class")
1656  self.cluster_obj = IMP.pmi.analysis.Clustering()
1657  self.cluster_obj.load_distance_matrix_file(
1658  file_name=distance_matrix_file)
1659  print("clustering with %s clusters" % str(number_of_clusters))
1660  self.cluster_obj.do_cluster(number_of_clusters)
1661  [best_score_feature_keyword_list_dict,
1662  rmf_file_name_index_dict] = self.load_objects(".macro.pkl")
1663  if display_plot:
1664  if self.rank == 0:
1665  self.cluster_obj.plot_matrix(figurename=os.path.join(
1666  outputdir, 'dist_matrix.pdf'))
1667  if exit_after_display:
1668  exit()
1669  if self.number_of_processes > 1:
1670  self.comm.Barrier()
1671 
1672 # ------------------------------------------------------------------------
1673 # now save all information about the clusters
1674 # ------------------------------------------------------------------------
1675 
1676  if self.rank == 0:
1677  print(self.cluster_obj.get_cluster_labels())
1678  for n, cl in enumerate(self.cluster_obj.get_cluster_labels()):
1679  print("rank %s " % str(self.rank))
1680  print("cluster %s " % str(n))
1681  print("cluster label %s " % str(cl))
1682  print(self.cluster_obj.get_cluster_label_names(cl))
1683  cluster_size = \
1684  len(self.cluster_obj.get_cluster_label_names(cl))
1685  cluster_prov = \
1686  prov + [IMP.pmi.io.ClusterProvenance(cluster_size)]
1687 
1688  # first initialize the Density class if requested
1689  if density_custom_ranges:
1690  DensModule = IMP.pmi.analysis.GetModelDensity(
1691  density_custom_ranges,
1692  voxel=voxel_size)
1693 
1694  dircluster = outputdir + "/cluster." + str(n) + "/"
1695  try:
1696  os.mkdir(dircluster)
1697  except: # noqa: E722
1698  pass
1699 
1700  rmsd_dict = {
1701  "AVERAGE_RMSD":
1702  str(self.cluster_obj.get_cluster_label_average_rmsd(cl))}
1703  clusstat = open(dircluster + "stat.out", "w")
1704  for k, structure_name in enumerate(
1705  self.cluster_obj.get_cluster_label_names(cl)):
1706  # extract the features
1707  tmp_dict = {}
1708  tmp_dict.update(rmsd_dict)
1709  index = rmf_file_name_index_dict[structure_name]
1710  for key in best_score_feature_keyword_list_dict:
1711  tmp_dict[
1712  key] = best_score_feature_keyword_list_dict[
1713  key][
1714  index]
1715 
1716  # get the rmf name and the frame number from the list of
1717  # frame names
1718  rmf_name = structure_name.split("|")[0]
1719  rmf_frame_number = int(structure_name.split("|")[1])
1720  clusstat.write(str(tmp_dict) + "\n")
1721 
1722  # extract frame (open or link to existing)
1723  if k == 0:
1724  prots, rs = \
1725  IMP.pmi.analysis.get_hiers_and_restraints_from_rmf(
1726  self.model, rmf_frame_number, rmf_name)
1727  else:
1728  linking_successful = \
1729  IMP.pmi.analysis.link_hiers_and_restraints_to_rmf(
1730  self.model, prots, rs, rmf_frame_number,
1731  rmf_name)
1732  if not linking_successful:
1733  continue
1734  if not prots:
1735  continue
1736 
1737  states = IMP.atom.get_by_type(
1738  prots[0], IMP.atom.STATE_TYPE)
1739  prot = states[state_number]
1740  if k == 0:
1741  IMP.pmi.io.add_provenance(cluster_prov, (prot,))
1742 
1743  # transform clusters onto first
1744  if k > 0:
1745  co = self.cluster_obj
1746  model_index = co.get_model_index_from_name(
1747  structure_name)
1748  transformation = co.get_transformation_to_first_member(
1749  cl, model_index)
1750  rbs = set()
1751  for p in IMP.atom.get_leaves(prot):
1752  if not IMP.core.XYZR.get_is_setup(p):
1754  IMP.core.XYZR(p).set_radius(0.0001)
1755  IMP.core.XYZR(p).set_coordinates((0, 0, 0))
1756 
1758  rbm = IMP.core.RigidBodyMember(p)
1759  rb = rbm.get_rigid_body()
1760  rbs.add(rb)
1761  else:
1763  transformation)
1764  for rb in rbs:
1765  IMP.core.transform(rb, transformation)
1766 
1767  # add the density
1768  if density_custom_ranges:
1769  DensModule.add_subunits_density(prot)
1770 
1771  # pdb writing should be optimized!
1772  o = IMP.pmi.output.Output()
1773  self.model.update()
1774  o.init_pdb(dircluster + str(k) + ".pdb", prot)
1775  o.write_pdb(dircluster + str(k) + ".pdb")
1776 
1777  # create a single-state System and write that
1779  IMP.Particle(self.model))
1780  h.set_name("System")
1781  h.add_child(prot)
1782  o.init_rmf(dircluster + str(k) + ".rmf3", [h], rs)
1783  o.write_rmf(dircluster + str(k) + ".rmf3")
1784  o.close_rmf(dircluster + str(k) + ".rmf3")
1785 
1786  del o
1787  # IMP.atom.destroy(prot)
1788 
1789  if density_custom_ranges:
1790  DensModule.write_mrc(path=dircluster)
1791  del DensModule
1792 
1793  if self.number_of_processes > 1:
1794  self.comm.Barrier()
1795 
1796  def get_cluster_rmsd(self, cluster_num):
1797  if self.cluster_obj is None:
1798  raise Exception("Run clustering first")
1799  return self.cluster_obj.get_cluster_label_average_rmsd(cluster_num)
1800 
1801  def save_objects(self, objects, file_name):
1802  import pickle
1803  with open(file_name, 'wb') as outf:
1804  pickle.dump(objects, outf)
1805 
1806  def load_objects(self, file_name):
1807  import pickle
1808  with open(file_name, 'rb') as inputf:
1809  objects = pickle.load(inputf)
1810  return objects
1811 
1812 
1814 
1815  """
1816  This class contains analysis utilities to investigate ReplicaExchange
1817  results.
1818  """
1819 
1820  ########################
1821  # Construction and Setup
1822  ########################
1823 
1824  def __init__(self, model, stat_files, best_models=None, score_key=None,
1825  alignment=True):
1826  """
1827  Construction of the Class.
1828  @param model IMP.Model()
1829  @param stat_files list of string. Can be ascii stat files,
1830  rmf files names
1831  @param best_models Integer. Number of best scoring models,
1832  if None: all models will be read
1833  @param score_key Use the provided stat key keyword as the score
1834  (by default, the total score is used)
1835  @param alignment boolean (Default=True). Align before computing
1836  the rmsd.
1837  """
1838 
1839  self.model = model
1840  self.best_models = best_models
1842  model, stat_files, self.best_models, score_key, cache=True)
1844  StatHierarchyHandler=self.stath0)
1845 
1846  self.rbs1, self.beads1 = IMP.pmi.tools.get_rbs_and_beads(
1848  self.rbs0, self.beads0 = IMP.pmi.tools.get_rbs_and_beads(
1850  self.sel0_rmsd = IMP.atom.Selection(self.stath0)
1851  self.sel1_rmsd = IMP.atom.Selection(self.stath1)
1852  self.sel0_alignment = IMP.atom.Selection(self.stath0)
1853  self.sel1_alignment = IMP.atom.Selection(self.stath1)
1854  self.clusters = []
1855  # fill the cluster list with a single cluster containing all models
1856  c = IMP.pmi.output.Cluster(0)
1857  self.clusters.append(c)
1858  for n0 in range(len(self.stath0)):
1859  c.add_member(n0)
1860  self.pairwise_rmsd = {}
1861  self.pairwise_molecular_assignment = {}
1862  self.alignment = alignment
1863  self.symmetric_molecules = {}
1864  self.issymmetricsel = {}
1865  self.update_seldicts()
1866  self.molcopydict0 = IMP.pmi.tools.get_molecules_dictionary_by_copy(
1867  IMP.atom.get_leaves(self.stath0))
1868  self.molcopydict1 = IMP.pmi.tools.get_molecules_dictionary_by_copy(
1869  IMP.atom.get_leaves(self.stath1))
1870 
1871  def set_rmsd_selection(self, **kwargs):
1872  """
1873  Setup the selection onto which the rmsd is computed
1874  @param kwargs use IMP.atom.Selection keywords
1875  """
1876  self.sel0_rmsd = IMP.atom.Selection(self.stath0, **kwargs)
1877  self.sel1_rmsd = IMP.atom.Selection(self.stath1, **kwargs)
1878  self.update_seldicts()
1879 
1880  def set_symmetric(self, molecule_name):
1881  """
1882  Store names of symmetric molecules
1883  """
1884  self.symmetric_molecules[molecule_name] = 0
1885  self.update_seldicts()
1886 
1887  def set_alignment_selection(self, **kwargs):
1888  """
1889  Setup the selection onto which the alignment is computed
1890  @param kwargs use IMP.atom.Selection keywords
1891  """
1892  self.sel0_alignment = IMP.atom.Selection(self.stath0, **kwargs)
1893  self.sel1_alignment = IMP.atom.Selection(self.stath1, **kwargs)
1894 
1895  ######################
1896  # Clustering functions
1897  ######################
1898  def clean_clusters(self):
1899  for c in self.clusters:
1900  del c
1901  self.clusters = []
1902 
1903  def cluster(self, rmsd_cutoff=10, metric=IMP.atom.get_rmsd):
1904  """
1905  Cluster the models based on RMSD.
1906  @param rmsd_cutoff Float the distance cutoff in Angstrom
1907  @param metric (Default=IMP.atom.get_rmsd) the metric that will
1908  be used to compute rmsds
1909  """
1910  self.clean_clusters()
1911  not_clustered = set(range(len(self.stath1)))
1912  while len(not_clustered) > 0:
1913  self.aggregate(not_clustered, rmsd_cutoff, metric)
1914  self.update_clusters()
1915 
1916  def refine(self, rmsd_cutoff=10):
1917  """
1918  Refine the clusters by merging the ones whose centers are close
1919  @param rmsd_cutoff cutoff distance in Angstorms
1920  """
1921  clusters_copy = self.clusters
1922  for c0, c1 in itertools.combinations(self.clusters, 2):
1923  if c0.center_index is None:
1924  self.compute_cluster_center(c0)
1925  if c1.center_index is None:
1926  self.compute_cluster_center(c1)
1927  _ = self.stath0[c0.center_index]
1928  _ = self.stath1[c1.center_index]
1929  rmsd, molecular_assignment = self.rmsd()
1930  if rmsd <= rmsd_cutoff:
1931  if c1 in self.clusters:
1932  clusters_copy.remove(c1)
1933  c0 += c1
1934  self.clusters = clusters_copy
1935  self.update_clusters()
1936 
1937  ####################
1938  # Input Output
1939  ####################
1940 
1941  def set_cluster_assignments(self, cluster_ids):
1942  if len(cluster_ids) != len(self.stath0):
1943  raise ValueError('cluster ids has to be same length as '
1944  'number of frames')
1945 
1946  self.clusters = []
1947  for i in sorted(list(set(cluster_ids))):
1948  self.clusters.append(IMP.pmi.output.Cluster(i))
1949  for i, (idx, d) in enumerate(zip(cluster_ids, self.stath0)):
1950  self.clusters[idx].add_member(i, d)
1951 
1952  def get_cluster_data(self, cluster):
1953  """
1954  Return the model data from a cluster
1955  @param cluster IMP.pmi.output.Cluster object
1956  """
1957  data = []
1958  for m in cluster:
1959  data.append(m)
1960  return data
1961 
1962  def save_data(self, filename='data.pkl'):
1963  """
1964  Save the data for the whole models into a pickle file
1965  @param filename string
1966  """
1967  self.stath0.save_data(filename)
1968 
1969  def set_data(self, data):
1970  """
1971  Set the data from an external IMP.pmi.output.Data
1972  @param data IMP.pmi.output.Data
1973  """
1974  self.stath0.data = data
1975  self.stath1.data = data
1976 
1977  def load_data(self, filename='data.pkl'):
1978  """
1979  Load the data from an external pickled file
1980  @param filename string
1981  """
1982  self.stath0.load_data(filename)
1983  self.stath1.load_data(filename)
1984  self.best_models = len(self.stath0)
1985 
1986  def add_cluster(self, rmf_name_list):
1987  c = IMP.pmi.output.Cluster(len(self.clusters))
1988  print("creating cluster index "+str(len(self.clusters)))
1989  self.clusters.append(c)
1990  current_len = len(self.stath0)
1991 
1992  for rmf in rmf_name_list:
1993  print("adding rmf "+rmf)
1994  self.stath0.add_stat_file(rmf)
1995  self.stath1.add_stat_file(rmf)
1996 
1997  for n0 in range(current_len, len(self.stath0)):
1998  d0 = self.stath0[n0]
1999  c.add_member(n0, d0)
2000  self.update_clusters()
2001 
2002  def save_clusters(self, filename='clusters.pkl'):
2003  """
2004  Save the clusters into a pickle file
2005  @param filename string
2006  """
2007  import pickle
2008  with open(filename, 'wb') as fl:
2009  pickle.dump(self.clusters, fl)
2010 
2011  def load_clusters(self, filename='clusters.pkl', append=False):
2012  """
2013  Load the clusters from a pickle file
2014  @param filename string
2015  @param append bool (Default=False), if True. append the clusters
2016  to the ones currently present
2017  """
2018  import pickle
2019  self.clean_clusters()
2020  with open(filename, 'rb') as fl:
2021  if append:
2022  self.clusters += pickle.load(fl)
2023  else:
2024  self.clusters = pickle.load(fl)
2025  self.update_clusters()
2026 
2027  ####################
2028  # Analysis Functions
2029  ####################
2030 
2031  def compute_cluster_center(self, cluster):
2032  """
2033  Compute the cluster center for a given cluster
2034  """
2035  member_distance = defaultdict(float)
2036 
2037  for n0, n1 in itertools.combinations(cluster.members, 2):
2038  _ = self.stath0[n0]
2039  _ = self.stath1[n1]
2040  rmsd, _ = self.rmsd()
2041  member_distance[n0] += rmsd
2042 
2043  if len(member_distance) > 0:
2044  cluster.center_index = min(member_distance,
2045  key=member_distance.get)
2046  else:
2047  cluster.center_index = cluster.members[0]
2048 
2049  def save_coordinates(self, cluster, rmf_name=None, reference="Absolute",
2050  prefix="./"):
2051  """
2052  Save the coordinates of the current cluster a single rmf file
2053  """
2054  print("saving coordinates", cluster)
2055  if self.alignment:
2056  self.set_reference(reference, cluster)
2057  o = IMP.pmi.output.Output()
2058  if rmf_name is None:
2059  rmf_name = prefix+'/'+str(cluster.cluster_id)+".rmf3"
2060 
2061  _ = self.stath1[cluster.members[0]]
2062  self.model.update()
2063  o.init_rmf(rmf_name, [self.stath1])
2064  for n1 in cluster.members:
2065  _ = self.stath1[n1]
2066  self.model.update()
2068  if self.alignment:
2069  self.align()
2070  o.write_rmf(rmf_name)
2072  o.close_rmf(rmf_name)
2073 
2074  def prune_redundant_structures(self, rmsd_cutoff=10):
2075  """
2076  remove structures that are similar
2077  append it to a new cluster
2078  """
2079  print("pruning models")
2080  selected = 0
2081  filtered = [selected]
2082  remaining = range(1, len(self.stath1), 10)
2083 
2084  while len(remaining) > 0:
2085  d0 = self.stath0[selected]
2086  rm = []
2087  for n1 in remaining:
2088  _ = self.stath1[n1]
2089  if self.alignment:
2090  self.align()
2091  d, _ = self.rmsd()
2092  if d <= rmsd_cutoff:
2093  rm.append(n1)
2094  print("pruning model %s, similar to model %s, rmsd %s"
2095  % (str(n1), str(selected), str(d)))
2096  remaining = [x for x in remaining if x not in rm]
2097  if len(remaining) == 0:
2098  break
2099  selected = remaining[0]
2100  filtered.append(selected)
2101  remaining.pop(0)
2102  c = IMP.pmi.output.Cluster(len(self.clusters))
2103  self.clusters.append(c)
2104  for n0 in filtered:
2105  d0 = self.stath0[n0]
2106  c.add_member(n0, d0)
2107  self.update_clusters()
2108 
2109  def precision(self, cluster):
2110  """
2111  Compute the precision of a cluster
2112  """
2113  npairs = 0
2114  rmsd = 0.0
2115  precision = None
2116 
2117  if cluster.center_index is not None:
2118  members1 = [cluster.center_index]
2119  else:
2120  members1 = cluster.members
2121 
2122  for n0 in members1:
2123  _ = self.stath0[n0]
2124  for n1 in cluster.members:
2125  if n0 != n1:
2126  npairs += 1
2127  _ = self.stath1[n1]
2129  tmp_rmsd, _ = self.rmsd()
2130  rmsd += tmp_rmsd
2132 
2133  if npairs > 0:
2134  precision = rmsd/npairs
2135  cluster.precision = precision
2136  return precision
2137 
2138  def bipartite_precision(self, cluster1, cluster2, verbose=False):
2139  """
2140  Compute the bipartite precision (ie the cross-precision)
2141  between two clusters
2142  """
2143  npairs = 0
2144  rmsd = 0.0
2145  for cn0, n0 in enumerate(cluster1.members):
2146  _ = self.stath0[n0]
2147  for cn1, n1 in enumerate(cluster2.members):
2148  _ = self.stath1[n1]
2149  tmp_rmsd, _ = self.rmsd()
2150  if verbose:
2151  print("--- rmsd between structure %s and structure "
2152  "%s is %s" % (str(cn0), str(cn1), str(tmp_rmsd)))
2153  rmsd += tmp_rmsd
2154  npairs += 1
2155  precision = rmsd/npairs
2156  return precision
2157 
2158  def rmsf(self, cluster, molecule, copy_index=0, state_index=0,
2159  cluster_ref=None, step=1):
2160  """
2161  Compute the Root mean square fluctuations
2162  of a molecule in a cluster
2163  Returns an IMP.pmi.tools.OrderedDict() where the keys are the
2164  residue indexes and the value is the rmsf
2165  """
2166  rmsf = IMP.pmi.tools.OrderedDict()
2167 
2168  # assumes that residue indexes are identical for stath0 and stath1
2169  if cluster_ref is not None:
2170  if cluster_ref.center_index is not None:
2171  members0 = [cluster_ref.center_index]
2172  else:
2173  members0 = cluster_ref.members
2174  else:
2175  if cluster.center_index is not None:
2176  members0 = [cluster.center_index]
2177  else:
2178  members0 = cluster.members
2179 
2180  s0 = IMP.atom.Selection(self.stath0, molecule=molecule, resolution=1,
2181  copy_index=copy_index, state_index=state_index)
2182  ps0 = s0.get_selected_particles()
2183  # get the residue indexes
2184  residue_indexes = list(IMP.pmi.tools.OrderedSet(
2185  [IMP.pmi.tools.get_residue_indexes(p)[0] for p in ps0]))
2186 
2187  # get the corresponding particles
2188  npairs = 0
2189  for n0 in members0:
2190  d0 = self.stath0[n0]
2191  for n1 in cluster.members[::step]:
2192  if n0 != n1:
2193  print("--- rmsf %s %s" % (str(n0), str(n1)))
2195 
2196  s1 = IMP.atom.Selection(
2197  self.stath1, molecule=molecule,
2198  residue_indexes=residue_indexes, resolution=1,
2199  copy_index=copy_index, state_index=state_index)
2200  ps1 = s1.get_selected_particles()
2201 
2202  d1 = self.stath1[n1]
2203  if self.alignment:
2204  self.align()
2205  for n, (p0, p1) in enumerate(zip(ps0, ps1)):
2206  r = residue_indexes[n]
2207  d0 = IMP.core.XYZ(p0)
2208  d1 = IMP.core.XYZ(p1)
2209  if r in rmsf:
2210  rmsf[r] += IMP.core.get_distance(d0, d1)
2211  else:
2212  rmsf[r] = IMP.core.get_distance(d0, d1)
2213  npairs += 1
2215  for r in rmsf:
2216  rmsf[r] /= npairs
2217 
2218  for stath in [self.stath0, self.stath1]:
2219  if molecule not in self.symmetric_molecules:
2220  s = IMP.atom.Selection(
2221  stath, molecule=molecule, residue_index=r,
2222  resolution=1, copy_index=copy_index,
2223  state_index=state_index)
2224  else:
2225  s = IMP.atom.Selection(
2226  stath, molecule=molecule, residue_index=r,
2227  resolution=1, state_index=state_index)
2228 
2229  ps = s.get_selected_particles()
2230  for p in ps:
2232  IMP.pmi.Uncertainty(p).set_uncertainty(rmsf[r])
2233  else:
2235 
2236  return rmsf
2237 
2238  def save_densities(self, cluster, density_custom_ranges, voxel_size=5,
2239  reference="Absolute", prefix="./", step=1):
2240  if self.alignment:
2241  self.set_reference(reference, cluster)
2242  dens = IMP.pmi.analysis.GetModelDensity(density_custom_ranges,
2243  voxel=voxel_size)
2244 
2245  for n1 in cluster.members[::step]:
2246  print("density "+str(n1))
2247  _ = self.stath1[n1]
2249  if self.alignment:
2250  self.align()
2251  dens.add_subunits_density(self.stath1)
2253  dens.write_mrc(path=prefix+'/', suffix=str(cluster.cluster_id))
2254  del dens
2255 
2256  def contact_map(self, cluster, contact_threshold=15, log_scale=False,
2257  consolidate=False, molecules=None, prefix='./',
2258  reference="Absolute"):
2259  if self.alignment:
2260  self.set_reference(reference, cluster)
2261  import numpy as np
2262  import matplotlib.pyplot as plt
2263  import matplotlib.cm as cm
2264  from scipy.spatial.distance import cdist
2265  import IMP.pmi.topology
2266  if molecules is None:
2268  for mol in IMP.pmi.tools.get_molecules(
2269  IMP.atom.get_leaves(self.stath1))]
2270  else:
2272  for mol in IMP.pmi.tools.get_molecules(
2274  self.stath1,
2275  molecules=molecules).get_selected_particles())]
2276  unique_copies = [mol for mol in mols if mol.get_copy_index() == 0]
2277  mol_names_unique = dict((mol.get_name(), mol) for mol in unique_copies)
2278  total_len_unique = sum(max(mol.get_residue_indexes())
2279  for mol in unique_copies)
2280 
2281  index_dict = {}
2282  prev_stop = 0
2283 
2284  if not consolidate:
2285  for mol in mols:
2286  seqlen = max(mol.get_residue_indexes())
2287  index_dict[mol] = range(prev_stop, prev_stop + seqlen)
2288  prev_stop += seqlen
2289 
2290  else:
2291  for mol in unique_copies:
2292  seqlen = max(mol.get_residue_indexes())
2293  index_dict[mol] = range(prev_stop, prev_stop + seqlen)
2294  prev_stop += seqlen
2295 
2296  for ncl, n1 in enumerate(cluster.members):
2297  print(ncl)
2298  _ = self.stath1[n1]
2299  coord_dict = IMP.pmi.tools.OrderedDict()
2300  for mol in mols:
2301  rindexes = mol.get_residue_indexes()
2302  coords = np.ones((max(rindexes), 3))
2303  for rnum in rindexes:
2304  sel = IMP.atom.Selection(mol, residue_index=rnum,
2305  resolution=1)
2306  selpart = sel.get_selected_particles()
2307  if len(selpart) == 0:
2308  continue
2309  selpart = selpart[0]
2310  coords[rnum - 1, :] = \
2311  IMP.core.XYZ(selpart).get_coordinates()
2312  coord_dict[mol] = coords
2313 
2314  if not consolidate:
2315  coords = np.concatenate(list(coord_dict.values()))
2316  dists = cdist(coords, coords)
2317  binary_dists = np.where((dists <= contact_threshold)
2318  & (dists >= 1.0), 1.0, 0.0)
2319  else:
2320  binary_dists_dict = {}
2321  for mol1 in mols:
2322  len1 = max(mol1.get_residue_indexes())
2323  for mol2 in mols:
2324  name1 = mol1.get_name()
2325  name2 = mol2.get_name()
2326  dists = cdist(coord_dict[mol1], coord_dict[mol2])
2327  if (name1, name2) not in binary_dists_dict:
2328  binary_dists_dict[(name1, name2)] = \
2329  np.zeros((len1, len1))
2330  binary_dists_dict[(name1, name2)] += \
2331  np.where((dists <= contact_threshold)
2332  & (dists >= 1.0), 1.0, 0.0)
2333  binary_dists = np.zeros((total_len_unique, total_len_unique))
2334 
2335  for name1, name2 in binary_dists_dict:
2336  r1 = index_dict[mol_names_unique[name1]]
2337  r2 = index_dict[mol_names_unique[name2]]
2338  binary_dists[min(r1):max(r1)+1, min(r2):max(r2)+1] = \
2339  np.where((binary_dists_dict[(name1, name2)] >= 1.0),
2340  1.0, 0.0)
2341 
2342  if ncl == 0:
2343  dist_maps = [dists]
2344  av_dist_map = dists
2345  contact_freqs = binary_dists
2346  else:
2347  dist_maps.append(dists)
2348  av_dist_map += dists
2349  contact_freqs += binary_dists
2350 
2351  if log_scale:
2352  contact_freqs = -np.log(1.0-1.0/(len(cluster)+1)*contact_freqs)
2353  else:
2354  contact_freqs = 1.0/len(cluster)*contact_freqs
2355  av_dist_map = 1.0/len(cluster)*contact_freqs
2356 
2357  fig = plt.figure(figsize=(100, 100))
2358  ax = fig.add_subplot(111)
2359  ax.set_xticks([])
2360  ax.set_yticks([])
2361  gap_between_components = 50
2362  colormap = cm.Blues
2363  colornorm = None
2364 
2365  if not consolidate:
2366  sorted_tuple = sorted(
2368  mol).get_extended_name(), mol) for mol in mols)
2369  prot_list = list(zip(*sorted_tuple))[1]
2370  else:
2371  sorted_tuple = sorted(
2372  (IMP.pmi.topology.PMIMoleculeHierarchy(mol).get_name(), mol)
2373  for mol in unique_copies)
2374  prot_list = list(zip(*sorted_tuple))[1]
2375 
2376  prot_listx = prot_list
2377  nresx = gap_between_components + \
2378  sum([max(mol.get_residue_indexes())
2379  + gap_between_components for mol in prot_listx])
2380 
2381  # set the list of proteins on the y axis
2382  prot_listy = prot_list
2383  nresy = gap_between_components + \
2384  sum([max(mol.get_residue_indexes())
2385  + gap_between_components for mol in prot_listy])
2386 
2387  # this is the residue offset for each protein
2388  resoffsetx = {}
2389  resendx = {}
2390  res = gap_between_components
2391  for mol in prot_listx:
2392  resoffsetx[mol] = res
2393  res += max(mol.get_residue_indexes())
2394  resendx[mol] = res
2395  res += gap_between_components
2396 
2397  resoffsety = {}
2398  resendy = {}
2399  res = gap_between_components
2400  for mol in prot_listy:
2401  resoffsety[mol] = res
2402  res += max(mol.get_residue_indexes())
2403  resendy[mol] = res
2404  res += gap_between_components
2405 
2406  resoffsetdiagonal = {}
2407  res = gap_between_components
2408  for mol in IMP.pmi.tools.OrderedSet(prot_listx + prot_listy):
2409  resoffsetdiagonal[mol] = res
2410  res += max(mol.get_residue_indexes())
2411  res += gap_between_components
2412 
2413  # plot protein boundaries
2414  xticks = []
2415  xlabels = []
2416  for n, prot in enumerate(prot_listx):
2417  res = resoffsetx[prot]
2418  end = resendx[prot]
2419  for proty in prot_listy:
2420  resy = resoffsety[proty]
2421  endy = resendy[proty]
2422  ax.plot([res, res], [resy, endy], linestyle='-',
2423  color='gray', lw=0.4)
2424  ax.plot([end, end], [resy, endy], linestyle='-',
2425  color='gray', lw=0.4)
2426  xticks.append((float(res) + float(end)) / 2)
2428  prot).get_extended_name())
2429 
2430  yticks = []
2431  ylabels = []
2432  for n, prot in enumerate(prot_listy):
2433  res = resoffsety[prot]
2434  end = resendy[prot]
2435  for protx in prot_listx:
2436  resx = resoffsetx[protx]
2437  endx = resendx[protx]
2438  ax.plot([resx, endx], [res, res], linestyle='-',
2439  color='gray', lw=0.4)
2440  ax.plot([resx, endx], [end, end], linestyle='-',
2441  color='gray', lw=0.4)
2442  yticks.append((float(res) + float(end)) / 2)
2444  prot).get_extended_name())
2445 
2446  # plot the contact map
2447 
2448  tmp_array = np.zeros((nresx, nresy))
2449  ret = {}
2450  for px in prot_listx:
2451  for py in prot_listy:
2452  resx = resoffsetx[px]
2453  lengx = resendx[px] - 1
2454  resy = resoffsety[py]
2455  lengy = resendy[py] - 1
2456  indexes_x = index_dict[px]
2457  minx = min(indexes_x)
2458  maxx = max(indexes_x)
2459  indexes_y = index_dict[py]
2460  miny = min(indexes_y)
2461  maxy = max(indexes_y)
2462  tmp_array[resx:lengx, resy:lengy] = \
2463  contact_freqs[minx:maxx, miny:maxy]
2464  ret[(px, py)] = np.argwhere(
2465  contact_freqs[minx:maxx, miny:maxy] == 1.0) + 1
2466 
2467  ax.imshow(tmp_array, cmap=colormap, norm=colornorm,
2468  origin='lower', alpha=0.6, interpolation='nearest')
2469 
2470  ax.set_xticks(xticks)
2471  ax.set_xticklabels(xlabels, rotation=90)
2472  ax.set_yticks(yticks)
2473  ax.set_yticklabels(ylabels)
2474  plt.setp(ax.get_xticklabels(), fontsize=6)
2475  plt.setp(ax.get_yticklabels(), fontsize=6)
2476 
2477  # display and write to file
2478  fig.set_size_inches(0.005 * nresx, 0.005 * nresy)
2479  [i.set_linewidth(2.0) for i in ax.spines.values()]
2480 
2481  plt.savefig(prefix+"/contact_map."+str(cluster.cluster_id)+".pdf",
2482  dpi=300, transparent="False")
2483  return ret
2484 
2485  def plot_rmsd_matrix(self, filename):
2486  self.compute_all_pairwise_rmsd()
2487  distance_matrix = np.zeros(
2488  (len(self.stath0), len(self.stath1)))
2489  for (n0, n1) in self.pairwise_rmsd:
2490  distance_matrix[n0, n1] = self.pairwise_rmsd[(n0, n1)]
2491 
2492  import matplotlib as mpl
2493  mpl.use('Agg')
2494  import matplotlib.pylab as pl
2495  from scipy.cluster import hierarchy as hrc
2496 
2497  fig = pl.figure(figsize=(10, 8))
2498  ax = fig.add_subplot(212)
2499  dendrogram = hrc.dendrogram(
2500  hrc.linkage(distance_matrix),
2501  color_threshold=7,
2502  no_labels=True)
2503  leaves_order = dendrogram['leaves']
2504  ax.set_xlabel('Model')
2505  ax.set_ylabel('RMSD [Angstroms]')
2506 
2507  ax2 = fig.add_subplot(221)
2508  cax = ax2.imshow(
2509  distance_matrix[leaves_order, :][:, leaves_order],
2510  interpolation='nearest')
2511  cb = fig.colorbar(cax)
2512  cb.set_label('RMSD [Angstroms]')
2513  ax2.set_xlabel('Model')
2514  ax2.set_ylabel('Model')
2515 
2516  pl.savefig(filename, dpi=300)
2517  pl.close(fig)
2518 
2519  ####################
2520  # Internal Functions
2521  ####################
2522 
2523  def update_clusters(self):
2524  """
2525  Update the cluster id numbers
2526  """
2527  for n, c in enumerate(self.clusters):
2528  c.cluster_id = n
2529 
2530  def get_molecule(self, hier, name, copy):
2531  s = IMP.atom.Selection(hier, molecule=name, copy_index=copy)
2532  return IMP.pmi.tools.get_molecules(s.get_selected_particles()[0])[0]
2533 
2534  def update_seldicts(self):
2535  """
2536  Update the seldicts
2537  """
2538  self.seldict0 = IMP.pmi.tools.get_selections_dictionary(
2539  self.sel0_rmsd.get_selected_particles())
2540  self.seldict1 = IMP.pmi.tools.get_selections_dictionary(
2541  self.sel1_rmsd.get_selected_particles())
2542  for mol in self.seldict0:
2543  for sel in self.seldict0[mol]:
2544  self.issymmetricsel[sel] = False
2545  for mol in self.symmetric_molecules:
2546  self.symmetric_molecules[mol] = len(self.seldict0[mol])
2547  for sel in self.seldict0[mol]:
2548  self.issymmetricsel[sel] = True
2549 
2550  def align(self):
2552  self.sel1_alignment, self.sel0_alignment)
2553 
2554  for rb in self.rbs1:
2555  IMP.core.transform(rb, tr)
2556 
2557  for bead in self.beads1:
2558  try:
2559  IMP.core.transform(IMP.core.XYZ(bead), tr)
2560  except: # noqa: E722
2561  continue
2562 
2563  self.model.update()
2564 
2565  def aggregate(self, idxs, rmsd_cutoff=10, metric=IMP.atom.get_rmsd):
2566  '''
2567  initial filling of the clusters.
2568  '''
2569  n0 = idxs.pop()
2570  print("clustering model "+str(n0))
2571  d0 = self.stath0[n0]
2572  c = IMP.pmi.output.Cluster(len(self.clusters))
2573  print("creating cluster index "+str(len(self.clusters)))
2574  self.clusters.append(c)
2575  c.add_member(n0, d0)
2576  clustered = set([n0])
2577  for n1 in idxs:
2578  print("--- trying to add model " + str(n1) + " to cluster "
2579  + str(len(self.clusters)))
2580  d1 = self.stath1[n1]
2581  if self.alignment:
2582  self.align()
2583  rmsd, _ = self.rmsd(metric=metric)
2584  if rmsd < rmsd_cutoff:
2585  print("--- model "+str(n1)+" added, rmsd="+str(rmsd))
2586  c.add_member(n1, d1)
2587  clustered.add(n1)
2588  else:
2589  print("--- model "+str(n1)+" NOT added, rmsd="+str(rmsd))
2590  idxs -= clustered
2591 
2592  def merge_aggregates(self, rmsd_cutoff, metric=IMP.atom.get_rmsd):
2593  """
2594  merge the clusters that have close members
2595 
2596  @param rmsd_cutoff cutoff distance in Angstorms
2597  @param metric Function to calculate distance between two Selections
2598  (by default, IMP.atom.get_rmsd is used)
2599  """
2600  # before merging, clusters are spheres of radius rmsd_cutoff
2601  # centered on the 1st element
2602  # here we only try to merge clusters whose centers are closer
2603  # than 2*rmsd_cutoff
2604  to_merge = []
2605  print("merging...")
2606  for c0, c1 in filter(lambda x: len(x[0].members) > 1,
2607  itertools.combinations(self.clusters, 2)):
2608  n0, n1 = [c.members[0] for c in (c0, c1)]
2609  _ = self.stath0[n0]
2610  _ = self.stath1[n1]
2611  rmsd, _ = self.rmsd()
2612  if (rmsd < 2*rmsd_cutoff and
2613  self.have_close_members(c0, c1, rmsd_cutoff, metric)):
2614  to_merge.append((c0, c1))
2615 
2616  for c0, c in reversed(to_merge):
2617  self.merge(c0, c)
2618 
2619  # keep only full clusters
2620  self.clusters = [c for c in
2621  filter(lambda x: len(x.members) > 0, self.clusters)]
2622 
2623  def have_close_members(self, c0, c1, rmsd_cutoff, metric):
2624  '''
2625  returns true if c0 and c1 have members that are closer than rmsd_cutoff
2626  '''
2627  print("check close members for clusters " + str(c0.cluster_id) +
2628  " and " + str(c1.cluster_id))
2629  for n0, n1 in itertools.product(c0.members[1:], c1.members):
2630  _ = self.stath0[n0]
2631  _ = self.stath1[n1]
2632  rmsd, _ = self.rmsd(metric=metric)
2633  if rmsd < rmsd_cutoff:
2634  return True
2635 
2636  return False
2637 
2638  def merge(self, c0, c1):
2639  '''
2640  merge two clusters
2641  '''
2642  c0 += c1
2643  c1.members = []
2644  c1.data = {}
2645 
2646  def rmsd_helper(self, sels0, sels1, metric):
2647  '''
2648  a function that returns the permutation best_sel of sels0 that
2649  minimizes metric
2650  '''
2651  best_rmsd2 = float('inf')
2652  best_sel = None
2653  if self.issymmetricsel[sels0[0]]:
2654  # this cases happens when symmetries were defined
2655  N = len(sels0)
2656  for offset in range(N):
2657  sels = [sels0[(offset+i) % N] for i in range(N)]
2658  sel0 = sels[0]
2659  sel1 = sels1[0]
2660  r = metric(sel0, sel1)
2661  rmsd2 = r*r*N
2662  if rmsd2 < best_rmsd2:
2663  best_rmsd2 = rmsd2
2664  best_sel = sels
2665  else:
2666  for sels in itertools.permutations(sels0):
2667  rmsd2 = 0.0
2668  for sel0, sel1 in itertools.takewhile(
2669  lambda x: rmsd2 < best_rmsd2, zip(sels, sels1)):
2670  r = metric(sel0, sel1)
2671  rmsd2 += r*r
2672  if rmsd2 < best_rmsd2:
2673  best_rmsd2 = rmsd2
2674  best_sel = sels
2675  return best_sel, best_rmsd2
2676 
2677  def compute_all_pairwise_rmsd(self):
2678  for d0 in self.stath0:
2679  for d1 in self.stath1:
2680  rmsd, _ = self.rmsd()
2681 
2682  def rmsd(self, metric=IMP.atom.get_rmsd):
2683  '''
2684  Computes the RMSD. Resolves ambiguous pairs assignments
2685  '''
2686  # here we memoize the rmsd and molecular assignment so that it's
2687  # not done multiple times
2688  n0 = self.stath0.current_index
2689  n1 = self.stath1.current_index
2690  if ((n0, n1) in self.pairwise_rmsd) \
2691  and ((n0, n1) in self.pairwise_molecular_assignment):
2692  return (self.pairwise_rmsd[(n0, n1)],
2693  self.pairwise_molecular_assignment[(n0, n1)])
2694 
2695  if self.alignment:
2696  self.align()
2697  # if it's not yet memoized
2698  total_rmsd = 0.0
2699  total_N = 0
2700  # this is a dictionary which keys are the molecule names, and values
2701  # are the list of IMP.atom.Selection for all molecules that share
2702  # the molecule name
2703  molecular_assignment = {}
2704  for molname, sels0 in self.seldict0.items():
2705  sels_best_order, best_rmsd2 = \
2706  self.rmsd_helper(sels0, self.seldict1[molname], metric)
2707 
2708  Ncoords = len(sels_best_order[0].get_selected_particles())
2709  Ncopies = len(self.seldict1[molname])
2710  total_rmsd += Ncoords*best_rmsd2
2711  total_N += Ncoords*Ncopies
2712 
2713  for sel0, sel1 in zip(sels_best_order, self.seldict1[molname]):
2714  p0 = sel0.get_selected_particles()[0]
2715  p1 = sel1.get_selected_particles()[0]
2716  m0 = IMP.pmi.tools.get_molecules([p0])[0]
2717  m1 = IMP.pmi.tools.get_molecules([p1])[0]
2718  c0 = IMP.atom.Copy(m0).get_copy_index()
2719  c1 = IMP.atom.Copy(m1).get_copy_index()
2720  molecular_assignment[(molname, c0)] = (molname, c1)
2721 
2722  total_rmsd = math.sqrt(total_rmsd/total_N)
2723 
2724  self.pairwise_rmsd[(n0, n1)] = total_rmsd
2725  self.pairwise_molecular_assignment[(n0, n1)] = molecular_assignment
2726  self.pairwise_rmsd[(n1, n0)] = total_rmsd
2727  self.pairwise_molecular_assignment[(n1, n0)] = molecular_assignment
2728  return total_rmsd, molecular_assignment
2729 
2730  def set_reference(self, reference, cluster):
2731  """
2732  Fix the reference structure for structural alignment, rmsd and
2733  chain assignment
2734 
2735  @param reference can be either "Absolute" (cluster center of the
2736  first cluster) or Relative (cluster center of the current
2737  cluster)
2738  #param cluster the reference IMP.pmi.output.Cluster object
2739  """
2740  if reference == "Absolute":
2741  _ = self.stath0[0]
2742  elif reference == "Relative":
2743  if cluster.center_index:
2744  n0 = cluster.center_index
2745  else:
2746  n0 = cluster.members[0]
2747  _ = self.stath0[n0]
2748 
2750  """
2751  compute the molecular assignments between multiple copies
2752  of the same sequence. It changes the Copy index of Molecules
2753  """
2754  _ = self.stath1[n1]
2755  _, molecular_assignment = self.rmsd()
2756  for (m0, c0), (m1, c1) in molecular_assignment.items():
2757  mol0 = self.molcopydict0[m0][c0]
2758  mol1 = self.molcopydict1[m1][c1]
2759  cik0 = IMP.atom.Copy(mol0).get_copy_index_key()
2760  p1 = IMP.atom.Copy(mol1).get_particle()
2761  p1.set_value(cik0, c0)
2762 
2764  """
2765  Undo the Copy index assignment
2766  """
2767  _ = self.stath1[n1]
2768  _, molecular_assignment = self.rmsd()
2769  for (m0, c0), (m1, c1) in molecular_assignment.items():
2770  mol0 = self.molcopydict0[m0][c0]
2771  mol1 = self.molcopydict1[m1][c1]
2772  cik0 = IMP.atom.Copy(mol0).get_copy_index_key()
2773  p1 = IMP.atom.Copy(mol1).get_particle()
2774  p1.set_value(cik0, c1)
2775 
2776  ####################
2777  # Container Functions
2778  ####################
2779 
2780  def __repr__(self):
2781  s = "AnalysisReplicaExchange\n"
2782  s += "---- number of clusters %s \n" % str(len(self.clusters))
2783  s += "---- number of models %s \n" % str(len(self.stath0))
2784  return s
2785 
2786  def __getitem__(self, int_slice_adaptor):
2787  if isinstance(int_slice_adaptor, int):
2788  return self.clusters[int_slice_adaptor]
2789  elif isinstance(int_slice_adaptor, slice):
2790  return self.__iter__(int_slice_adaptor)
2791  else:
2792  raise TypeError("Unknown Type")
2793 
2794  def __len__(self):
2795  return len(self.clusters)
2796 
2797  def __iter__(self, slice_key=None):
2798  if slice_key is None:
2799  for i in range(len(self)):
2800  yield self[i]
2801  else:
2802  for i in range(len(self))[slice_key]:
2803  yield self[i]
Simplify creation of constraints and movers for an IMP Hierarchy.
def rmsd
Computes the RMSD.
Definition: macros.py:2682
def set_reference
Fix the reference structure for structural alignment, rmsd and chain assignment.
Definition: macros.py:2730
def load_clusters
Load the clusters from a pickle file.
Definition: macros.py:2011
A class to implement Hamiltonian Replica Exchange.
def select_at_all_resolutions
Perform selection using the usual keywords but return ALL resolutions (BEADS and GAUSSIANS).
Definition: pmi/tools.py:1062
def precision
Compute the precision of a cluster.
Definition: macros.py:2109
CheckLevel get_check_level()
Get the current audit mode.
Definition: exception.h:80
Extends the functionality of IMP.atom.Molecule.
A macro for running all the basic operations of analysis.
Definition: macros.py:1129
def get_restraint_set
Get a RestraintSet containing all PMI restraints added to the model.
Definition: pmi/tools.py:104
A container for models organized into clusters.
Definition: output.py:1559
Sample using molecular dynamics.
Definition: samplers.py:256
def aggregate
initial filling of the clusters.
Definition: macros.py:2565
A member of a rigid body, it has internal (local) coordinates.
Definition: rigid_bodies.h:540
A macro to help setup and run replica exchange.
Definition: macros.py:157
static bool get_is_setup(const IMP::ParticleAdaptor &p)
Definition: rigid_bodies.h:541
Set of Python classes to create a multi-state, multi-resolution IMP hierarchy.
def prune_redundant_structures
remove structures that are similar append it to a new cluster
Definition: macros.py:2074
def rmsf
Compute the Root mean square fluctuations of a molecule in a cluster Returns an IMP.pmi.tools.OrderedDict() where the keys are the residue indexes and the value is the rmsf.
Definition: macros.py:2158
static XYZR setup_particle(Model *m, ParticleIndex pi)
Definition: XYZR.h:48
Utility classes and functions for reading and storing PMI files.
def get_best_models
Given a list of stat files, read them all and find the best models.
def get_molecules
This function returns the parent molecule hierarchies of given objects.
Definition: pmi/tools.py:1159
A helper output for model evaluation.
Miscellaneous utilities.
Definition: pmi/tools.py:1
def set_rmsd_selection
Setup the selection onto which the rmsd is computed.
Definition: macros.py:1871
def get_cluster_data
Return the model data from a cluster.
Definition: macros.py:1952
def __init__
Construction of the Class.
Definition: macros.py:1824
def get_molecules
Return list of all molecules grouped by state.
Definition: macros.py:1033
def set_data
Set the data from an external IMP.pmi.output.Data.
Definition: macros.py:1969
def undo_apply_molecular_assignments
Undo the Copy index assignment.
Definition: macros.py:2763
def set_alignment_selection
Setup the selection onto which the alignment is computed.
Definition: macros.py:1887
def rmsd_helper
a function that returns the permutation best_sel of sels0 that minimizes metric
Definition: macros.py:2646
def save_coordinates
Save the coordinates of the current cluster a single rmf file.
Definition: macros.py:2049
def clustering
Get the best scoring models, compute a distance matrix, cluster them, and create density maps...
Definition: macros.py:1272
def apply_molecular_assignments
compute the molecular assignments between multiple copies of the same sequence.
Definition: macros.py:2749
This class contains analysis utilities to investigate ReplicaExchange results.
Definition: macros.py:1813
Add uncertainty to a particle.
Definition: Uncertainty.h:24
A macro to build a IMP::pmi::topology::System based on a TopologyReader object.
Definition: macros.py:820
def set_restart
Enable a simulation to be restarted if it is interrupted.
Definition: macros.py:371
def merge_aggregates
merge the clusters that have close members
Definition: macros.py:2592
Represent the root node of the global IMP.atom.Hierarchy.
double get_distance(XYZR a, XYZR b)
Compute the sphere distance between a and b.
Definition: XYZR.h:89
A class to cluster structures.
def add_protocol_output
Capture details of the modeling protocol.
Definition: macros.py:1181
static Uncertainty setup_particle(Model *m, ParticleIndex pi, Float uncertainty)
Definition: Uncertainty.h:45
def compute_cluster_center
Compute the cluster center for a given cluster.
Definition: macros.py:2031
static bool get_is_setup(const IMP::ParticleAdaptor &p)
Definition: XYZR.h:47
def get_modeling_trajectory
Get a trajectory of the modeling run, for generating demonstrative movies.
Definition: macros.py:1192
Warning related to handling of structures.
A decorator for keeping track of copies of a molecule.
Definition: Copy.h:28
static Hierarchy setup_particle(Model *m, ParticleIndex pi, ParticleIndexesAdaptor children=ParticleIndexesAdaptor())
Create a Hierarchy of level t by adding the needed attributes.
def get_trajectory_models
Given a list of stat files, read them all and find a trajectory of models.
def __init__
Constructor.
Definition: macros.py:232
The standard decorator for manipulating molecular structures.
Performs alignment and RMSD calculation for two sets of coordinates.
Definition: pmi/Analysis.py:21
def update_seldicts
Update the seldicts.
Definition: macros.py:2534
def update_clusters
Update the cluster id numbers.
Definition: macros.py:2523
def scatter_and_gather
Synchronize data over a parallel run.
Definition: pmi/tools.py:542
void transform(XYZ a, const algebra::Transformation3D &tr)
Apply a transformation to the particle.
Code that uses the MPI parallel library.
def restart_replica_exchange
Continue a failed ReplicaExchange sampling run.
Definition: macros.py:789
def refine
Refine the clusters by merging the ones whose centers are close.
Definition: macros.py:1916
A decorator for a particle with x,y,z coordinates.
Definition: XYZ.h:30
Class for easy writing of PDBs, RMFs, and stat files.
Definition: output.py:199
Collect timing information.
Definition: pmi/tools.py:119
def set_symmetric
Store names of symmetric molecules.
Definition: macros.py:1880
Warning for an expected, but missing, file.
Tools for clustering and cluster analysis.
Definition: pmi/Analysis.py:1
Transformation3D get_identity_transformation_3d()
Return a transformation that does not do anything.
Classes for writing output files and processing them.
Definition: output.py:1
def deprecated_object
Python decorator to mark a class as deprecated.
Definition: __init__.py:11930
Sampling of the system.
Definition: samplers.py:1
Sample using Monte Carlo.
Definition: samplers.py:70
Create movers and set up constraints for PMI objects.
def merge
merge two clusters
Definition: macros.py:2638
def add_state
Add a state using the topology info in a IMP::pmi::topology::TopologyReader object.
Definition: macros.py:873
The general base class for IMP exceptions.
Definition: exception.h:48
static SampleProvenance setup_particle(Model *m, ParticleIndex pi, std::string method, int frames, int iterations, int replicas)
Definition: provenance.h:266
class to link stat files to several rmf files
Definition: output.py:1307
Mapping between FASTA one-letter codes and residue types.
Definition: alphabets.py:1
def save_data
Save the data for the whole models into a pickle file.
Definition: macros.py:1962
Class to handle individual particles of a Model object.
Definition: Particle.h:45
def execute_macro
Builds representations and sets up degrees of freedom.
Definition: macros.py:1044
def bipartite_precision
Compute the bipartite precision (ie the cross-precision) between two clusters.
Definition: macros.py:2138
def read_coordinates_of_rmfs
Read in coordinates of a set of RMF tuples.
def __init__
Constructor.
Definition: macros.py:849
int get_copy_index(Hierarchy h)
Walk up the hierarchy to find the current copy index.
def cluster
Cluster the models based on RMSD.
Definition: macros.py:1903
static bool get_is_setup(Model *m, ParticleIndex pi)
Definition: Uncertainty.h:30
def save_clusters
Save the clusters into a pickle file.
Definition: macros.py:2002
def have_close_members
returns true if c0 and c1 have members that are closer than rmsd_cutoff
Definition: macros.py:2623
void add_geometries(RMF::FileHandle file, const display::GeometriesTemp &r)
Add geometries to the file.
algebra::Transformation3D get_transformation_aligning_first_to_second(const Selection &s1, const Selection &s2)
Get the transformation to align two selections.
A dictionary-like wrapper for reading and storing sequence data.
def get_rbs_and_beads
Returns unique objects in original order.
Definition: pmi/tools.py:1135
void add_provenance(Model *m, ParticleIndex pi, Provenance p)
Add provenance to part of the model.
Hierarchies get_leaves(const Selection &h)
Select hierarchy particles identified by the biological name.
Definition: Selection.h:70
Compute mean density maps from structures.
def load_data
Load the data from an external pickled file.
Definition: macros.py:1977
Support for the RMF file format for storing hierarchical molecular data and markup.
def get_residue_indexes
Retrieve the residue indexes for the given particle.
Definition: pmi/tools.py:499
Sample using replica exchange.
Definition: samplers.py:375
Warning for probably incorrect input parameters.
def add_provenance
Add provenance information in prov (a list of _TempProvenance objects) to each of the IMP hierarchies...
Inferential scoring building on methods developed as part of the Inferential Structure Determination ...
A decorator for a particle with x,y,z coordinates and a radius.
Definition: XYZR.h:27