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