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