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