1 """@namespace IMP.pmi.mmcif
2 @brief Support for the mmCIF file format.
4 IMP has basic support for writing out files in mmCIF format, for
5 deposition in [PDB-Dev](https://pdb-dev.wwpdb.org/).
6 mmCIF files are currently generated by creating an
7 IMP.pmi.mmcif.ProtocolOutput class, and attaching it to an
8 IMP.pmi.representation.Representation object, after which any
9 generated models and metadata are collected and output as mmCIF.
12 from __future__
import print_function
39 import ihm.representation
41 import ihm.cross_linkers
44 def _assign_id(obj, seen_objs, obj_by_id):
45 """Assign a unique ID to obj, and track all ids in obj_by_id."""
46 if obj
not in seen_objs:
47 if not hasattr(obj,
'id'):
49 obj.id = len(obj_by_id)
50 seen_objs[obj] = obj.id
52 obj.id = seen_objs[obj]
55 def _get_by_residue(p):
56 """Determine whether the given particle represents a specific residue
57 or a more coarse-grained object."""
61 class _ComponentMapper(object):
62 """Map a Particle to a component name"""
63 def __init__(self, prot):
66 self.name =
'cif-output'
67 self.o.dictionary_pdbs[self.name] = self.prot
68 self.o._init_dictchain(self.name, self.prot, multichar_chain=
True)
70 def __getitem__(self, p):
71 protname, is_a_bead = self.o.get_prot_name_from_particle(self.name, p)
75 class _AsymMapper(object):
76 """Map a Particle to an asym_unit"""
77 def __init__(self, simo, prot):
79 self._cm = _ComponentMapper(prot)
80 self._seen_ranges = {}
82 def __getitem__(self, p):
83 protname = self._cm[p]
84 return self.simo.asym_units[protname]
86 def get_feature(self, ps):
87 """Get an ihm.restraint.Feature that covers the given particles"""
95 rng = asym(rind, rind)
99 rng = asym(rinds[0], rinds[-1])
101 raise ValueError(
"Unsupported particle type %s" % str(p))
103 if len(rngs) > 0
and rngs[-1].asym == asym \
104 and rngs[-1].seq_id_range[1] == rng.seq_id_range[0] - 1:
105 rngs[-1].seq_id_range = (rngs[-1].seq_id_range[0],
112 if hrngs
in self._seen_ranges:
113 return self._seen_ranges[hrngs]
115 feat = ihm.restraint.ResidueFeature(rngs)
116 self._seen_ranges[hrngs] = feat
120 class _AllSoftware(object):
121 def __init__(self, system):
123 self.modeller_used = self.phyre2_used =
False
124 self.pmi = ihm.Software(
125 name=
"IMP PMI module",
126 version=IMP.pmi.__version__,
127 classification=
"integrative model building",
128 description=
"integrative model building",
129 location=
'https://integrativemodeling.org')
130 self.imp = ihm.Software(
131 name=
"Integrative Modeling Platform (IMP)",
132 version=IMP.__version__,
133 classification=
"integrative model building",
134 description=
"integrative model building",
135 location=
'https://integrativemodeling.org')
138 if hasattr(self.imp,
'citation'):
139 if sys.version_info[0] > 2:
141 javi =
'Vel\u00e1zquez-Muriel J'
143 javi =
'Velazquez-Muriel J'
144 self.imp.citation = ihm.Citation(
146 title=
'Putting the pieces together: integrative modeling '
147 'platform software for structure determination of '
148 'macromolecular assemblies',
149 journal=
'PLoS Biol', volume=10, page_range=
'e1001244',
151 authors=[
'Russel D',
'Lasker K',
'Webb B', javi,
'Tjioe E',
152 'Schneidman-Duhovny D',
'Peterson B',
'Sali A'],
153 doi=
'10.1371/journal.pbio.1001244')
154 self.pmi.citation = ihm.Citation(
156 title=
'Modeling Biological Complexes Using Integrative '
157 'Modeling Platform.',
158 journal=
'Methods Mol Biol', volume=2022, page_range=(353, 377),
160 authors=[
'Saltzberg D',
'Greenberg CH',
'Viswanath S',
161 'Chemmama I',
'Webb B',
'Pellarin R',
'Echeverria I',
163 doi=
'10.1007/978-1-4939-9608-7_15')
164 self.system.software.extend([self.pmi, self.imp])
166 def set_modeller_used(self, version, date):
167 if self.modeller_used:
169 self.modeller_used =
True
171 name=
'MODELLER', classification=
'comparative modeling',
172 description=
'Comparative modeling by satisfaction '
173 'of spatial restraints, build ' + date,
174 location=
'https://salilab.org/modeller/', version=version)
175 self.system.software.append(s)
176 if hasattr(s,
'citation'):
177 s.citation = ihm.Citation(
179 title=
'Comparative protein modelling by satisfaction of '
180 'spatial restraints.',
181 journal=
'J Mol Biol', volume=234, page_range=(779, 815),
182 year=1993, authors=[
'Sali A',
'Blundell TL'],
183 doi=
'10.1006/jmbi.1993.1626')
185 def set_phyre2_used(self):
188 self.phyre2_used =
True
190 name=
'Phyre2', classification=
'protein homology modeling',
191 description=
'Protein Homology/analogY Recognition Engine V 2.0',
192 version=
'2.0', location=
'http://www.sbg.bio.ic.ac.uk/~phyre2/')
193 if hasattr(s,
'citation'):
194 s.citation = ihm.Citation(
196 title=
'The Phyre2 web portal for protein modeling, '
197 'prediction and analysis.',
198 journal=
'Nat Protoc', volume=10, page_range=(845, 858),
199 authors=[
'Kelley LA',
'Mezulis S',
'Yates CM',
'Wass MN',
201 year=2015, doi=
'10.1038/nprot.2015.053')
202 self.system.software.append(s)
205 def _get_fragment_is_rigid(fragment):
206 """Determine whether a fragment is modeled rigidly"""
212 class _PDBFragment(ihm.representation.ResidueSegment):
213 """Record details about part of a PDB file used as input
215 def __init__(self, state, component, start, end, pdb_offset,
216 pdbname, chain, hier, asym_unit):
218 super(_PDBFragment, self).__init__(
219 asym_unit=asym_unit.pmi_range(start, end),
220 rigid=
None, primitive=
'sphere')
221 self.component, self.start, self.end, self.offset, self.pdbname \
222 = component, start, end, pdb_offset, pdbname
223 self.state, self.chain, self.hier = state, chain, hier
228 rigid = property(
lambda self: _get_fragment_is_rigid(self),
229 lambda self, val:
None)
231 def combine(self, other):
235 class _BeadsFragment(ihm.representation.FeatureSegment):
236 """Record details about beads used to represent part of a component."""
239 def __init__(self, state, component, start, end, count, hier, asym_unit):
240 super(_BeadsFragment, self).__init__(
241 asym_unit=asym_unit(start, end), rigid=
None, primitive=
'sphere',
243 self.state, self.component, self.hier = state, component, hier
245 rigid = property(
lambda self: _get_fragment_is_rigid(self),
246 lambda self, val:
None)
248 def combine(self, other):
250 if (type(other) == type(self)
and
251 other.asym_unit.seq_id_range[0]
252 == self.asym_unit.seq_id_range[1] + 1):
253 self.asym_unit.seq_id_range = (self.asym_unit.seq_id_range[0],
254 other.asym_unit.seq_id_range[1])
255 self.count += other.count
259 class _AllModelRepresentations(object):
260 def __init__(self, simo):
264 self.fragments = OrderedDict()
265 self._all_representations = {}
267 def copy_component(self, state, name, original, asym_unit):
268 """Copy all representation for `original` in `state` to `name`"""
271 newf.asym_unit = asym_unit(*f.asym_unit.seq_id_range)
273 for rep
in self.fragments:
274 if original
in self.fragments[rep]:
275 if name
not in self.fragments[rep]:
276 self.fragments[rep][name] = OrderedDict()
277 self.fragments[rep][name][state] = [
278 copy_frag(f)
for f
in self.fragments[rep][original][state]]
281 first_state = list(self.fragments[rep][name].keys())[0]
282 if state
is first_state:
283 representation = self._all_representations[rep]
284 representation.extend(self.fragments[rep][name][state])
286 def add_fragment(self, state, representation, fragment):
287 """Add a model fragment."""
288 comp = fragment.component
289 id_rep = id(representation)
290 self._all_representations[id_rep] = representation
291 if id_rep
not in self.fragments:
292 self.fragments[id_rep] = OrderedDict()
293 if comp
not in self.fragments[id_rep]:
294 self.fragments[id_rep][comp] = OrderedDict()
295 if state
not in self.fragments[id_rep][comp]:
296 self.fragments[id_rep][comp][state] = []
297 fragments = self.fragments[id_rep][comp][state]
298 if len(fragments) == 0
or not fragments[-1].combine(fragment):
299 fragments.append(fragment)
302 first_state = list(self.fragments[id_rep][comp].keys())[0]
303 if state
is first_state:
304 representation.append(fragment)
307 class _AllDatasets(object):
308 """Track all datasets generated by PMI and add them to the ihm.System"""
309 def __init__(self, system):
312 self._datasets_by_state = {}
313 self._restraints_by_state = {}
315 def get_all_group(self, state):
316 """Get a DatasetGroup encompassing all datasets so far in this state"""
320 g = ihm.dataset.DatasetGroup(
321 self._datasets_by_state.get(state, [])
322 + [r.dataset
for r
in self._restraints_by_state.get(state, [])
326 def add(self, state, dataset):
327 """Add a new dataset."""
328 self._datasets.append(dataset)
329 if state
not in self._datasets_by_state:
330 self._datasets_by_state[state] = []
331 self._datasets_by_state[state].append(dataset)
334 self.system.orphan_datasets.append(dataset)
338 """Add the dataset for a restraint"""
339 if state
not in self._restraints_by_state:
340 self._restraints_by_state[state] = []
341 self._restraints_by_state[state].append(restraint)
344 class _CrossLinkRestraint(ihm.restraint.CrossLinkRestraint):
345 """Restrain to a set of cross-links"""
348 _label_map = {
'wtDSS':
'DSS',
'scDSS':
'DSS',
'scEDC':
'EDC'}
349 _descriptor_map = {
'DSS': ihm.cross_linkers.dss,
350 'EDC': ihm.cross_linkers.edc}
352 def __init__(self, pmi_restraint):
353 self.pmi_restraint = pmi_restraint
356 linker = getattr(self.pmi_restraint,
'linker',
None)
357 label = self.pmi_restraint.label
359 super(_CrossLinkRestraint, self).__init__(
360 dataset=self.pmi_restraint.dataset,
361 linker=linker
or self._get_chem_descriptor(label))
364 def _get_chem_descriptor(cls, label):
366 label = cls._label_map.get(label, label)
367 if label
not in cls._descriptor_map:
371 d = ihm.ChemDescriptor(label)
372 cls._descriptor_map[label] = d
373 return cls._descriptor_map[label]
375 def _set_psi_sigma(self, model):
378 if model.m != self.pmi_restraint.model:
380 for resolution
in self.pmi_restraint.sigma_dictionary:
381 statname =
'ISDCrossLinkMS_Sigma_%s_%s' % (resolution, self.label)
382 if model.stats
and statname
in model.stats:
383 sigma = float(model.stats[statname])
384 p = self.pmi_restraint.sigma_dictionary[resolution][0]
385 old_values.append((p, p.get_scale()))
387 for psiindex
in self.pmi_restraint.psi_dictionary:
388 statname =
'ISDCrossLinkMS_Psi_%s_%s' % (psiindex, self.label)
389 if model.stats
and statname
in model.stats:
390 psi = float(model.stats[statname])
391 p = self.pmi_restraint.psi_dictionary[psiindex][0]
392 old_values.append((p, p.get_scale()))
396 return list(reversed(old_values))
398 def add_fits_from_model_statfile(self, model):
400 old_values = self._set_psi_sigma(model)
404 for xl
in self.cross_links:
406 xl.fits[model] = ihm.restraint.CrossLinkFit(
407 psi=xl.psi, sigma1=xl.sigma1, sigma2=xl.sigma2)
409 for p, scale
in old_values:
413 def __set_dataset(self, val):
414 self.pmi_restraint.dataset = val
415 dataset = property(
lambda self: self.pmi_restraint.dataset,
419 def get_asym_mapper_for_state(simo, state, asym_map):
420 asym = asym_map.get(state,
None)
422 asym = _AsymMapper(simo, state.prot)
423 asym_map[state] = asym
427 class _PMICrossLink(object):
430 psi = property(
lambda self: self.psi_p.get_scale(),
431 lambda self, val:
None)
432 sigma1 = property(
lambda self: self.sigma1_p.get_scale(),
433 lambda self, val:
None)
434 sigma2 = property(
lambda self: self.sigma2_p.get_scale(),
435 lambda self, val:
None)
438 class _ResidueCrossLink(ihm.restraint.ResidueCrossLink, _PMICrossLink):
442 class _FeatureCrossLink(ihm.restraint.FeatureCrossLink, _PMICrossLink):
446 class _EM2DRestraint(ihm.restraint.EM2DRestraint):
447 def __init__(self, state, pmi_restraint, image_number, resolution,
448 pixel_size, image_resolution, projection_number,
450 self.pmi_restraint, self.image_number = pmi_restraint, image_number
451 super(_EM2DRestraint, self).__init__(
452 dataset=pmi_restraint.datasets[image_number],
453 assembly=state.modeled_assembly,
454 segment=
False, number_raw_micrographs=micrographs_number,
455 pixel_size_width=pixel_size, pixel_size_height=pixel_size,
456 image_resolution=image_resolution,
457 number_of_projections=projection_number)
460 def __get_dataset(self):
461 return self.pmi_restraint.datasets[self.image_number]
463 def __set_dataset(self, val):
464 self.pmi_restraint.datasets[self.image_number] = val
466 dataset = property(__get_dataset, __set_dataset)
468 def add_fits_from_model_statfile(self, model):
469 ccc = self._get_cross_correlation(model)
470 transform = self._get_transformation(model)
471 rot = transform.get_rotation()
472 rm = [[e
for e
in rot.get_rotation_matrix_row(i)]
for i
in range(3)]
473 self.fits[model] = ihm.restraint.EM2DRestraintFit(
474 cross_correlation_coefficient=ccc,
476 tr_vector=transform.get_translation())
478 def _get_transformation(self, model):
479 """Get the transformation that places the model on the image"""
480 stats = model.em2d_stats
or model.stats
481 prefix =
'ElectronMicroscopy2D_%s_Image%d' % (self.pmi_restraint.label,
482 self.image_number + 1)
483 r = [float(stats[prefix +
'_Rotation%d' % i])
for i
in range(4)]
484 t = [float(stats[prefix +
'_Translation%d' % i])
488 inv = model.transform.get_inverse()
492 def _get_cross_correlation(self, model):
493 """Get the cross correlation coefficient between the model projection
495 stats = model.em2d_stats
or model.stats
496 return float(stats[
'ElectronMicroscopy2D_%s_Image%d_CCC'
497 % (self.pmi_restraint.label,
498 self.image_number + 1)])
501 class _EM3DRestraint(ihm.restraint.EM3DRestraint):
503 def __init__(self, simo, state, pmi_restraint, target_ps, densities):
504 self.pmi_restraint = pmi_restraint
505 super(_EM3DRestraint, self).__init__(
506 dataset=pmi_restraint.dataset,
507 assembly=self._get_assembly(densities, simo, state),
508 fitting_method=
'Gaussian mixture models',
509 number_of_gaussians=len(target_ps))
512 def __set_dataset(self, val):
513 self.pmi_restraint.dataset = val
514 dataset = property(
lambda self: self.pmi_restraint.dataset,
517 def _get_assembly(self, densities, simo, state):
518 """Get the Assembly that this restraint acts on"""
519 cm = _ComponentMapper(state.prot)
522 components[cm[d]] =
None
523 a = simo._get_subassembly(
524 components, name=
"EM subassembly",
525 description=
"All components that fit the EM map")
528 def add_fits_from_model_statfile(self, model):
529 ccc = self._get_cross_correlation(model)
530 self.fits[model] = ihm.restraint.EM3DRestraintFit(
531 cross_correlation_coefficient=ccc)
533 def _get_cross_correlation(self, model):
534 """Get the cross correlation coefficient between the model
536 if model.stats
is not None:
537 return float(model.stats[
'GaussianEMRestraint_%s_CCC'
538 % self.pmi_restraint.label])
541 class _GeometricRestraint(ihm.restraint.GeometricRestraint):
543 def __init__(self, simo, state, pmi_restraint, geometric_object,
544 feature, distance, sigma):
545 self.pmi_restraint = pmi_restraint
546 super(_GeometricRestraint, self).__init__(
547 dataset=pmi_restraint.dataset,
548 geometric_object=geometric_object, feature=feature,
549 distance=distance, harmonic_force_constant=1. / sigma,
553 def __set_dataset(self, val):
554 self.pmi_restraint.dataset = val
555 dataset = property(
lambda self: self.pmi_restraint.dataset,
559 class _ReplicaExchangeProtocolStep(ihm.protocol.Step):
560 def __init__(self, state, rex):
561 if rex.monte_carlo_sample_objects
is not None:
562 method =
'Replica exchange monte carlo'
564 method =
'Replica exchange molecular dynamics'
565 self.monte_carlo_temperature = rex.vars[
'monte_carlo_temperature']
566 self.replica_exchange_minimum_temperature = \
567 rex.vars[
'replica_exchange_minimum_temperature']
568 self.replica_exchange_maximum_temperature = \
569 rex.vars[
'replica_exchange_maximum_temperature']
570 super(_ReplicaExchangeProtocolStep, self).__init__(
571 assembly=state.modeled_assembly,
573 method=method, name=
'Sampling',
574 num_models_begin=
None,
575 num_models_end=rex.vars[
"number_of_frames"],
576 multi_scale=
True, multi_state=
False, ordered=
False)
579 class _ReplicaExchangeProtocolDumper(ihm.dumper.Dumper):
580 """Write IMP-specific information about replica exchange to mmCIF.
581 Note that IDs will have already been assigned by python-ihm's
582 standard modeling protocol dumper."""
583 def dump(self, system, writer):
584 with writer.loop(
"_imp_replica_exchange_protocol",
585 [
"protocol_id",
"step_id",
"monte_carlo_temperature",
586 "replica_exchange_minimum_temperature",
587 "replica_exchange_maximum_temperature"])
as lp:
588 for p
in system._all_protocols():
590 if isinstance(s, _ReplicaExchangeProtocolStep):
591 self._dump_step(p, s, lp)
593 def _dump_step(self, p, s, lp):
594 mintemp = s.replica_exchange_minimum_temperature
595 maxtemp = s.replica_exchange_maximum_temperature
596 lp.write(protocol_id=p._id, step_id=s._id,
597 monte_carlo_temperature=s.monte_carlo_temperature,
598 replica_exchange_minimum_temperature=mintemp,
599 replica_exchange_maximum_temperature=maxtemp)
602 class _ReplicaExchangeProtocolHandler(ihm.reader.Handler):
603 category =
'_imp_replica_exchange_protocol'
605 """Read IMP-specific information about replica exchange from mmCIF."""
606 def __call__(self, protocol_id, step_id, monte_carlo_temperature,
607 replica_exchange_minimum_temperature,
608 replica_exchange_maximum_temperature):
609 p = self.sysr.protocols.get_by_id(protocol_id)
611 s = p.steps[int(step_id)-1]
613 s.__class__ = _ReplicaExchangeProtocolStep
614 s.monte_carlo_temperature = \
615 self.get_float(monte_carlo_temperature)
616 s.replica_exchange_minimum_temperature = \
617 self.get_float(replica_exchange_minimum_temperature)
618 s.replica_exchange_maximum_temperature = \
619 self.get_float(replica_exchange_maximum_temperature)
622 class _SimpleProtocolStep(ihm.protocol.Step):
623 def __init__(self, state, num_models_end, method):
624 super(_SimpleProtocolStep, self).__init__(
625 assembly=state.modeled_assembly,
627 method=method, name=
'Sampling',
628 num_models_begin=
None,
629 num_models_end=num_models_end,
630 multi_scale=
True, multi_state=
False, ordered=
False)
633 class _Chain(object):
634 """Represent a single chain in a Model"""
635 def __init__(self, pmi_chain_id, asym_unit):
636 self.pmi_chain_id, self.asym_unit = pmi_chain_id, asym_unit
640 def add(self, xyz, atom_type, residue_type, residue_index,
641 all_indexes, radius):
642 if atom_type
is None:
643 self.spheres.append((xyz, residue_type, residue_index,
644 all_indexes, radius))
646 self.atoms.append((xyz, atom_type, residue_type, residue_index,
647 all_indexes, radius))
648 orig_comp = property(
lambda self: self.comp)
651 class _TransformedChain(object):
652 """Represent a chain that is a transformed version of another"""
653 def __init__(self, orig_chain, asym_unit, transform):
654 self.orig_chain, self.asym_unit = orig_chain, asym_unit
655 self.transform = transform
657 def __get_spheres(self):
658 for (xyz, residue_type, residue_index, all_indexes,
659 radius)
in self.orig_chain.spheres:
660 yield (self.transform * xyz, residue_type, residue_index,
662 spheres = property(__get_spheres)
664 def __get_atoms(self):
665 for (xyz, atom_type, residue_type, residue_index, all_indexes,
666 radius)
in self.orig_chain.atoms:
667 yield (self.transform * xyz, atom_type, residue_type,
668 residue_index, all_indexes, radius)
669 atoms = property(__get_atoms)
671 entity = property(
lambda self: self.orig_chain.entity)
672 orig_comp = property(
lambda self: self.orig_chain.comp)
675 class _Excluder(object):
676 def __init__(self, component, simo):
677 self._seqranges = simo._exclude_coords.get(component, [])
679 def is_excluded(self, indexes):
680 """Return True iff the given sequence range is excluded."""
681 for seqrange
in self._seqranges:
682 if indexes[0] >= seqrange[0]
and indexes[-1] <= seqrange[1]:
686 class _Model(ihm.model.Model):
687 def __init__(self, prot, simo, protocol, assembly, representation):
688 super(_Model, self).__init__(assembly=assembly, protocol=protocol,
689 representation=representation)
690 self.simo = weakref.proxy(simo)
696 self.em2d_stats =
None
699 self._is_restrained =
True
702 self.m = prot.get_model()
703 o.dictionary_pdbs[name] = prot
704 o._init_dictchain(name, prot, multichar_chain=
True)
705 (particle_infos_for_pdb,
706 self.geometric_center) = o.get_particle_infos_for_pdb_writing(name)
708 self._make_spheres_atoms(particle_infos_for_pdb, o, name, simo)
711 def all_chains(self, simo):
712 """Yield all chains, including transformed ones"""
714 for c
in self.chains:
716 chain_for_comp[c.comp] = c
717 for tc
in simo._transformed_components:
718 orig_chain = chain_for_comp.get(tc.original,
None)
720 asym = simo.asym_units[tc.name]
721 c = _TransformedChain(orig_chain, asym, tc.transform)
725 def _make_spheres_atoms(self, particle_infos_for_pdb, o, name, simo):
726 entity_for_chain = {}
729 for protname, chain_id
in o.dictchain[name].items():
730 if protname
in simo.entities:
731 entity_for_chain[chain_id] = simo.entities[protname]
734 pn = protname.split(
'.')[0]
735 entity_for_chain[chain_id] = simo.entities[pn]
736 comp_for_chain[chain_id] = protname
740 correct_asym[chain_id] = simo.asym_units[protname]
747 for (xyz, atom_type, residue_type, chain_id, residue_index,
748 all_indexes, radius)
in particle_infos_for_pdb:
749 if chain
is None or chain.pmi_chain_id != chain_id:
750 chain = _Chain(chain_id, correct_asym[chain_id])
751 chain.entity = entity_for_chain[chain_id]
752 chain.comp = comp_for_chain[chain_id]
753 self.chains.append(chain)
754 excluder = _Excluder(chain.comp, simo)
755 if not excluder.is_excluded(all_indexes
if all_indexes
756 else [residue_index]):
757 chain.add(xyz, atom_type, residue_type, residue_index,
760 def parse_rmsf_file(self, fname, component):
761 self.rmsf[component] = rmsf = {}
762 with open(fname)
as fh:
764 resnum, blocknum, val = line.split()
765 rmsf[int(resnum)] = (int(blocknum), float(val))
767 def get_rmsf(self, component, indexes):
768 """Get the RMSF value for the given residue indexes."""
771 rmsf = self.rmsf[component]
772 blocknums = dict.fromkeys(rmsf[ind][0]
for ind
in indexes)
773 if len(blocknums) != 1:
774 raise ValueError(
"Residue indexes %s aren't all in the same block"
776 return rmsf[indexes[0]][1]
779 for chain
in self.all_chains(self.simo):
780 pmi_offset = chain.asym_unit.entity.pmi_offset
781 for atom
in chain.atoms:
782 (xyz, atom_type, residue_type, residue_index,
783 all_indexes, radius) = atom
784 pt = self.transform * xyz
785 yield ihm.model.Atom(
786 asym_unit=chain.asym_unit,
787 seq_id=residue_index - pmi_offset,
788 atom_id=atom_type.get_string(),
790 x=pt[0], y=pt[1], z=pt[2])
792 def get_spheres(self):
793 for chain
in self.all_chains(self.simo):
794 pmi_offset = chain.asym_unit.entity.pmi_offset
795 for sphere
in chain.spheres:
796 (xyz, residue_type, residue_index,
797 all_indexes, radius) = sphere
798 if all_indexes
is None:
799 all_indexes = (residue_index,)
800 pt = self.transform * xyz
801 yield ihm.model.Sphere(
802 asym_unit=chain.asym_unit,
803 seq_id_range=(all_indexes[0] - pmi_offset,
804 all_indexes[-1] - pmi_offset),
805 x=pt[0], y=pt[1], z=pt[2], radius=radius,
806 rmsf=self.get_rmsf(chain.orig_comp, all_indexes))
809 class _AllProtocols(object):
810 def __init__(self, simo):
811 self.simo = weakref.proxy(simo)
813 self.protocols = OrderedDict()
815 def add_protocol(self, state):
816 """Add a new Protocol"""
817 if state
not in self.protocols:
818 self.protocols[state] = []
819 p = ihm.protocol.Protocol()
820 self.simo.system.orphan_protocols.append(p)
821 self.protocols[state].append(p)
823 def add_step(self, step, state):
824 """Add a ProtocolStep to the last Protocol of the given State"""
825 if state
not in self.protocols:
826 self.add_protocol(state)
827 protocol = self.get_last_protocol(state)
828 if len(protocol.steps) == 0:
829 step.num_models_begin = 0
831 step.num_models_begin = protocol.steps[-1].num_models_end
832 protocol.steps.append(step)
833 step.id = len(protocol.steps)
835 step.dataset_group = self.simo.all_datasets.get_all_group(state)
837 def add_postproc(self, step, state):
838 """Add a postprocessing step to the last protocol"""
839 protocol = self.get_last_protocol(state)
840 if len(protocol.analyses) == 0:
841 protocol.analyses.append(ihm.analysis.Analysis())
842 protocol.analyses[-1].steps.append(step)
844 def get_last_protocol(self, state):
845 """Return the most recently-added _Protocol"""
846 return self.protocols[state][-1]
849 class _AllStartingModels(object):
850 def __init__(self, simo):
854 self.models = OrderedDict()
857 def add_pdb_fragment(self, fragment):
858 """Add a starting model PDB fragment."""
859 comp = fragment.component
860 state = fragment.state
861 if comp
not in self.models:
862 self.models[comp] = OrderedDict()
863 if state
not in self.models[comp]:
864 self.models[comp][state] = []
865 models = self.models[comp][state]
866 if len(models) == 0 \
867 or models[-1].fragments[0].pdbname != fragment.pdbname:
868 model = self._add_model(fragment)
872 models[-1].fragments.append(weakref.proxy(fragment))
876 pmi_offset = models[-1].asym_unit.entity.pmi_offset
877 sid_begin = min(fragment.start - pmi_offset,
878 models[-1].asym_unit.seq_id_range[0])
879 sid_end = max(fragment.end - pmi_offset,
880 models[-1].asym_unit.seq_id_range[1])
881 models[-1].asym_unit = fragment.asym_unit.asym(sid_begin, sid_end)
882 fragment.starting_model = models[-1]
884 def _add_model(self, f):
885 parser = ihm.metadata.PDBParser()
886 r = parser.parse_file(f.pdbname)
888 self.simo._add_dataset(r[
'dataset'])
890 templates = r[
'templates'].get(f.chain, [])
893 self.simo.system.locations.append(t.alignment_file)
895 self.simo._add_dataset(t.dataset)
896 source = r[
'entity_source'].get(f.chain)
898 f.asym_unit.entity.source = source
899 pmi_offset = f.asym_unit.entity.pmi_offset
901 asym_unit=f.asym_unit.asym.pmi_range(f.start, f.end),
902 dataset=r[
'dataset'], asym_id=f.chain,
903 templates=templates, offset=f.offset + pmi_offset,
904 metadata=r[
'metadata'],
905 software=r[
'software'][0]
if r[
'software']
else None,
906 script_file=r[
'script'])
907 m.fragments = [weakref.proxy(f)]
911 class _StartingModel(ihm.startmodel.StartingModel):
912 def get_seq_dif(self):
916 pmi_offset = self.asym_unit.entity.pmi_offset
917 mh = IMP.mmcif.data._StartingModelAtomHandler(self.templates,
919 for f
in self.fragments:
922 residue_indexes=list(range(f.start - f.offset,
923 f.end - f.offset + 1)))
924 for a
in mh.get_ihm_atoms(sel.get_selected_particles(),
925 f.offset - pmi_offset):
927 self._seq_dif = mh._seq_dif
930 class _ReplicaExchangeAnalysisPostProcess(ihm.analysis.ClusterStep):
931 """Post processing using AnalysisReplicaExchange0 macro"""
933 def __init__(self, rex, num_models_begin):
936 for fname
in self.get_all_stat_files():
937 with open(fname)
as fh:
938 num_models_end += len(fh.readlines())
939 super(_ReplicaExchangeAnalysisPostProcess, self).__init__(
940 feature=
'RMSD', num_models_begin=num_models_begin,
941 num_models_end=num_models_end)
943 def get_stat_file(self, cluster_num):
944 return os.path.join(self.rex._outputdir,
"cluster.%d" % cluster_num,
947 def get_all_stat_files(self):
948 for i
in range(self.rex._number_of_clusters):
949 yield self.get_stat_file(i)
952 class _ReplicaExchangeAnalysisEnsemble(ihm.model.Ensemble):
953 """Ensemble generated using AnalysisReplicaExchange0 macro"""
955 num_models_deposited =
None
957 def __init__(self, pp, cluster_num, model_group, num_deposit):
958 with open(pp.get_stat_file(cluster_num))
as fh:
959 num_models = len(fh.readlines())
960 super(_ReplicaExchangeAnalysisEnsemble, self).__init__(
961 num_models=num_models,
962 model_group=model_group, post_process=pp,
963 clustering_feature=pp.feature,
964 name=model_group.name)
965 self.cluster_num = cluster_num
966 self.num_models_deposited = num_deposit
968 def get_rmsf_file(self, component):
969 return os.path.join(self.post_process.rex._outputdir,
970 'cluster.%d' % self.cluster_num,
971 'rmsf.%s.dat' % component)
973 def load_rmsf(self, model, component):
974 fname = self.get_rmsf_file(component)
975 if os.path.exists(fname):
976 model.parse_rmsf_file(fname, component)
978 def get_localization_density_file(self, fname):
979 return os.path.join(self.post_process.rex._outputdir,
980 'cluster.%d' % self.cluster_num,
983 def load_localization_density(self, state, fname, select_tuple,
985 fullpath = self.get_localization_density_file(fname)
986 if os.path.exists(fullpath):
987 details =
"Localization density for %s %s" \
988 % (fname, self.model_group.name)
989 local_file = ihm.location.OutputFileLocation(fullpath,
991 for s
in select_tuple:
992 if isinstance(s, tuple)
and len(s) == 3:
993 asym = asym_units[s[2]].pmi_range(s[0], s[1])
996 den = ihm.model.LocalizationDensity(file=local_file,
998 self.densities.append(den)
1000 def load_all_models(self, simo, state):
1001 stat_fname = self.post_process.get_stat_file(self.cluster_num)
1003 with open(stat_fname)
as fh:
1004 stats = ast.literal_eval(fh.readline())
1006 rmf_file = os.path.join(os.path.dirname(stat_fname),
1007 "%d.rmf3" % model_num)
1009 if os.path.exists(rmf_file):
1010 rh = RMF.open_rmf_file_read_only(rmf_file)
1011 system = state._pmi_object.system
1017 if model_num >= self.num_models_deposited:
1021 def _get_precision(self):
1022 precfile = os.path.join(self.post_process.rex._outputdir,
1023 "precision.%d.%d.out" % (self.cluster_num,
1025 if not os.path.exists(precfile):
1029 r'All .*/cluster.%d/ average centroid distance ([\d\.]+)'
1031 with open(precfile)
as fh:
1035 return float(m.group(1))
1037 precision = property(
lambda self: self._get_precision(),
1038 lambda self, val:
None)
1041 class _SimpleEnsemble(ihm.model.Ensemble):
1042 """Simple manually-created ensemble"""
1044 num_models_deposited =
None
1046 def __init__(self, pp, model_group, num_models, drmsd,
1047 num_models_deposited, ensemble_file):
1048 super(_SimpleEnsemble, self).__init__(
1049 model_group=model_group, post_process=pp, num_models=num_models,
1050 file=ensemble_file, precision=drmsd, name=model_group.name,
1051 clustering_feature=
'dRMSD')
1052 self.num_models_deposited = num_models_deposited
1054 def load_localization_density(self, state, component, asym, local_file):
1055 den = ihm.model.LocalizationDensity(file=local_file, asym_unit=asym)
1056 self.densities.append(den)
1059 class _CustomDNAAlphabet(object):
1060 """Custom DNA alphabet that maps A,C,G,T (rather than DA,DC,DG,DT
1061 as in python-ihm)"""
1062 _comps = dict([cc.code_canonical, cc]
1063 for cc
in ihm.DNAAlphabet._comps.values())
1066 class _EntityMapper(dict):
1067 """Handle mapping from IMP components (without copy number) to CIF
1068 entities. Multiple components may map to the same entity if they
1070 def __init__(self, system):
1071 super(_EntityMapper, self).__init__()
1072 self._sequence_dict = {}
1074 self.system = system
1076 def _get_alphabet(self, alphabet):
1077 """Map a PMI alphabet to an IHM alphabet"""
1081 alphabet_map = {
None: ihm.LPeptideAlphabet,
1082 IMP.pmi.alphabets.amino_acid: ihm.LPeptideAlphabet,
1083 IMP.pmi.alphabets.rna: ihm.RNAAlphabet,
1084 IMP.pmi.alphabets.dna: _CustomDNAAlphabet}
1085 if alphabet
in alphabet_map:
1086 return alphabet_map[alphabet]
1088 raise TypeError(
"Don't know how to handle %s" % alphabet)
1090 def add(self, component_name, sequence, offset, alphabet):
1091 def entity_seq(sequence):
1094 return [
'UNK' if s ==
'X' else s
for s
in sequence]
1097 if sequence
not in self._sequence_dict:
1100 d = component_name.split(
"@")[0].split(
".")[0]
1101 entity = Entity(entity_seq(sequence), description=d,
1103 alphabet=self._get_alphabet(alphabet))
1104 self.system.entities.append(entity)
1105 self._sequence_dict[sequence] = entity
1106 self[component_name] = self._sequence_dict[sequence]
1109 class _TransformedComponent(object):
1110 def __init__(self, name, original, transform):
1111 self.name, self.original, self.transform = name, original, transform
1114 class _SimpleRef(object):
1115 """Class with similar interface to weakref.ref, but keeps a strong ref"""
1116 def __init__(self, ref):
1123 class _State(ihm.model.State):
1124 """Representation of a single state in the system."""
1126 def __init__(self, pmi_object, po):
1131 self._pmi_object = weakref.proxy(pmi_object)
1132 if hasattr(pmi_object,
'state'):
1135 self._pmi_state = _SimpleRef(pmi_object.state)
1137 self._pmi_state = weakref.ref(pmi_object)
1139 old_name = self.name
1140 super(_State, self).__init__(experiment_type=
'Fraction of bulk')
1141 self.name = old_name
1145 self.modeled_assembly = ihm.Assembly(
1146 name=
"Modeled assembly",
1147 description=self.get_postfixed_name(
1148 "All components modeled by IMP"))
1149 po.system.orphan_assemblies.append(self.modeled_assembly)
1151 self.all_modeled_components = []
1154 return hash(self._pmi_state())
1156 def __eq__(self, other):
1157 return self._pmi_state() == other._pmi_state()
1159 def add_model_group(self, group):
1163 def get_prefixed_name(self, name):
1164 """Prefix the given name with the state name, if available."""
1166 return self.short_name +
' ' + name
1170 return name[0].upper() + name[1:]
if name
else ''
1172 def get_postfixed_name(self, name):
1173 """Postfix the given name with the state name, if available."""
1175 return "%s in state %s" % (name, self.short_name)
1179 short_name = property(
lambda self: self._pmi_state().short_name)
1180 long_name = property(
lambda self: self._pmi_state().long_name)
1182 def __get_name(self):
1183 return self._pmi_state().long_name
1185 def __set_name(self, val):
1186 self._pmi_state().long_name = val
1188 name = property(__get_name, __set_name)
1192 """A single entity in the system.
1193 This functions identically to the base ihm.Entity class, but it
1194 allows identifying residues by either the IHM numbering scheme
1195 (seq_id, which is always contiguous starting at 1) or by the PMI
1196 scheme (which is similar, but may not start at 1 if the sequence in
1197 the FASTA file has one or more N-terminal gaps"""
1198 def __init__(self, sequence, pmi_offset, *args, **keys):
1201 self.pmi_offset = pmi_offset
1202 super(Entity, self).__init__(sequence, *args, **keys)
1205 """Return a single IHM residue indexed using PMI numbering"""
1206 return self.residue(res_id - self.pmi_offset)
1209 """Return a range of IHM residues indexed using PMI numbering"""
1210 off = self.pmi_offset
1211 return self(res_id_begin - off, res_id_end - off)
1215 """A single asymmetric unit in the system.
1216 This functions identically to the base ihm.AsymUnit class, but it
1217 allows identifying residues by either the IHM numbering scheme
1218 (seq_id, which is always contiguous starting at 1) or by the PMI
1219 scheme (which is similar, but may not start at 1 if the sequence in
1220 the FASTA file has one or more N-terminal gaps"""
1222 def __init__(self, entity, *args, **keys):
1223 super(AsymUnit, self).__init__(
1224 entity, auth_seq_id_map=entity.pmi_offset, *args, **keys)
1227 """Return a single IHM residue indexed using PMI numbering"""
1228 return self.residue(res_id - self.entity.pmi_offset)
1231 """Return a range of IHM residues indexed using PMI numbering"""
1232 off = self.entity.pmi_offset
1233 return self(res_id_begin - off, res_id_end - off)
1237 """Class to encode a modeling protocol as mmCIF.
1239 IMP has basic support for writing out files in mmCIF format, for
1240 deposition in [PDB-Dev](https://pdb-dev.wwpdb.org/).
1241 After creating an instance of this class, attach it to an
1242 IMP.pmi.topology.System object. After this, any
1243 generated models and metadata are automatically collected in the
1244 `system` attribute, which is an
1245 [ihm.System](https://python-ihm.readthedocs.io/en/latest/main.html#ihm.System) object.
1246 Once the protocol is complete, call finalize() to make sure `system`
1247 contains everything, then use the
1248 [python-ihm API](https://python-ihm.readthedocs.io/en/latest/dumper.html#ihm.dumper.write)
1249 to write out files in mmCIF or BinaryCIF format.
1251 See also get_handlers(), get_dumpers().
1255 self.system = ihm.System()
1256 self._state_group = ihm.model.StateGroup()
1257 self.system.state_groups.append(self._state_group)
1259 self._state_ensemble_offset = 0
1260 self._main_script = os.path.abspath(sys.argv[0])
1263 loc = ihm.location.WorkflowFileLocation(
1264 path=self._main_script,
1265 details=
"The main integrative modeling script")
1266 self.system.locations.append(loc)
1269 self.__asym_states = {}
1270 self._working_directory = os.getcwd()
1272 "Default representation")
1273 self.entities = _EntityMapper(self.system)
1275 self.asym_units = {}
1276 self._all_components = {}
1277 self.all_modeled_components = []
1278 self._transformed_components = []
1279 self.sequence_dict = {}
1282 self._xy_plane = ihm.geometry.XYPlane()
1283 self._xz_plane = ihm.geometry.XZPlane()
1284 self._z_axis = ihm.geometry.ZAxis()
1285 self._center_origin = ihm.geometry.Center(0, 0, 0)
1286 self._identity_transform = ihm.geometry.Transformation.identity()
1289 self._exclude_coords = {}
1291 self.all_representations = _AllModelRepresentations(self)
1292 self.all_protocols = _AllProtocols(self)
1293 self.all_datasets = _AllDatasets(self.system)
1294 self.all_starting_models = _AllStartingModels(self)
1296 self.all_software = _AllSoftware(self.system)
1299 """Create a new Representation and return it. This can be
1300 passed to add_model(), add_bead_element() or add_pdb_element()."""
1301 r = ihm.representation.Representation(name=name)
1302 self.system.orphan_representations.append(r)
1306 """Don't record coordinates for the given domain.
1307 Coordinates for the given domain (specified by a component name
1308 and a 2-element tuple giving the start and end residue numbers)
1309 will be excluded from the mmCIF file. This can be used to exclude
1310 parts of the structure that weren't well resolved in modeling.
1311 Any bead or residue that lies wholly within this range will be
1312 excluded. Multiple ranges for a given component can be excluded
1313 by calling this method multiple times."""
1314 if component
not in self._exclude_coords:
1315 self._exclude_coords[component] = []
1316 self._exclude_coords[component].append(seqrange)
1318 def _is_excluded(self, component, start, end):
1319 """Return True iff this chunk of sequence should be excluded"""
1320 for seqrange
in self._exclude_coords.get(component, ()):
1321 if start >= seqrange[0]
and end <= seqrange[1]:
1324 def _add_state(self, state):
1325 """Create a new state and return a pointer to it."""
1326 self._state_ensemble_offset = len(self.system.ensembles)
1327 s = _State(state, self)
1328 self._state_group.append(s)
1329 self._last_state = s
1332 def _get_chain_for_component(self, name, output):
1333 """Get the chain ID for a component, if any."""
1335 if name
in self.asym_units:
1336 return self.asym_units[name]._id
1341 def _get_assembly_comps(self, assembly):
1342 """Get the names of the components in the given assembly"""
1346 comps[ca.details] =
None
1350 """Make a new component that's a transformed copy of another.
1351 All representation for the existing component is copied to the
1353 assembly_comps = self._get_assembly_comps(state.modeled_assembly)
1354 if name
in assembly_comps:
1355 raise ValueError(
"Component %s already exists" % name)
1356 elif original
not in assembly_comps:
1357 raise ValueError(
"Original component %s does not exist" % original)
1358 self.create_component(state, name,
True)
1359 self.add_component_sequence(state, name, self.sequence_dict[original])
1360 self._transformed_components.append(_TransformedComponent(
1361 name, original, transform))
1362 self.all_representations.copy_component(state, name, original,
1363 self.asym_units[name])
1365 def create_component(self, state, name, modeled, asym_name=None):
1366 if asym_name
is None:
1368 new_comp = name
not in self._all_components
1369 self._all_components[name] =
None
1371 state.all_modeled_components.append(name)
1372 if asym_name
not in self.asym_units:
1374 self.asym_units[asym_name] =
None
1376 self.all_modeled_components.append(name)
1378 def add_component_sequence(self, state, name, seq, asym_name=None,
1380 if asym_name
is None:
1383 def get_offset(seq):
1385 for i
in range(len(seq)):
1388 seq, offset = get_offset(seq)
1389 if name
in self.sequence_dict:
1390 if self.sequence_dict[name] != seq:
1391 raise ValueError(
"Sequence mismatch for component %s" % name)
1393 self.sequence_dict[name] = seq
1394 self.entities.add(name, seq, offset, alphabet)
1395 if asym_name
in self.asym_units:
1396 if self.asym_units[asym_name]
is None:
1398 entity = self.entities[name]
1399 asym =
AsymUnit(entity, details=asym_name)
1400 self.system.asym_units.append(asym)
1401 self.asym_units[asym_name] = asym
1402 state.modeled_assembly.append(self.asym_units[asym_name])
1404 def _add_restraint_model_fits(self):
1405 """Add fits to restraints for all known models"""
1406 for group, m
in self.system._all_models():
1407 if m._is_restrained:
1408 for r
in self.system.restraints:
1409 if hasattr(r,
'add_fits_from_model_statfile'):
1410 r.add_fits_from_model_statfile(m)
1413 """Do any final processing on the class hierarchy.
1414 After calling this method, the `system` member (an instance
1415 of `ihm.System`) completely reproduces the PMI modeling, and
1416 can be written out to an mmCIF file with `ihm.dumper.write`,
1417 and/or modified using the ihm API."""
1418 self._add_restraint_model_fits()
1420 def add_pdb_element(self, state, name, start, end, offset, pdbname,
1421 chain, hier, representation=
None):
1422 if self._is_excluded(name, start, end):
1424 if representation
is None:
1425 representation = self.default_representation
1426 asym = self.asym_units[name]
1427 p = _PDBFragment(state, name, start, end, offset, pdbname, chain,
1429 self.all_representations.add_fragment(state, representation, p)
1430 self.all_starting_models.add_pdb_fragment(p)
1432 def add_bead_element(self, state, name, start, end, num, hier,
1433 representation=
None):
1434 if self._is_excluded(name, start, end):
1436 if representation
is None:
1437 representation = self.default_representation
1438 asym = self.asym_units[name]
1439 pmi_offset = asym.entity.pmi_offset
1440 b = _BeadsFragment(state, name, start - pmi_offset, end - pmi_offset,
1442 self.all_representations.add_fragment(state, representation, b)
1444 def get_cross_link_group(self, pmi_restraint):
1445 r = _CrossLinkRestraint(pmi_restraint)
1446 self.system.restraints.append(r)
1447 self._add_restraint_dataset(r)
1450 def add_experimental_cross_link(self, r1, c1, r2, c2, rsr):
1451 if c1
not in self._all_components
or c2
not in self._all_components:
1457 e1 = self.entities[c1]
1458 e2 = self.entities[c2]
1459 xl = ihm.restraint.ExperimentalCrossLink(residue1=e1.pmi_residue(r1),
1460 residue2=e2.pmi_residue(r2))
1461 rsr.experimental_cross_links.append([xl])
1464 def add_cross_link(self, state, ex_xl, p1, p2, length, sigma1_p, sigma2_p,
1467 asym = get_asym_mapper_for_state(self, state, self.__asym_states)
1468 d = ihm.restraint.UpperBoundDistanceRestraint(length)
1470 if _get_by_residue(p1)
and _get_by_residue(p2):
1471 cls = _ResidueCrossLink
1473 cls = _FeatureCrossLink
1474 xl = cls(ex_xl, asym1=asym[p1], asym2=asym[p2], distance=d,
1477 xl.psi_p, xl.sigma1_p, xl.sigma2_p = psi_p, sigma1_p, sigma2_p
1478 rsr.cross_links.append(xl)
1480 def add_replica_exchange(self, state, rex):
1485 step = _ReplicaExchangeProtocolStep(state, rex)
1486 step.software = self.all_software.pmi
1487 self.all_protocols.add_step(step, state)
1489 def _add_simple_dynamics(self, num_models_end, method):
1491 state = self._last_state
1492 self.all_protocols.add_step(_SimpleProtocolStep(state, num_models_end,
1495 def _add_protocol(self):
1497 state = self._last_state
1498 self.all_protocols.add_protocol(state)
1500 def _add_dataset(self, dataset):
1501 return self.all_datasets.add(self._last_state, dataset)
1503 def _add_restraint_dataset(self, restraint):
1504 return self.all_datasets.add_restraint(self._last_state, restraint)
1506 def _add_simple_postprocessing(self, num_models_begin, num_models_end):
1508 state = self._last_state
1509 pp = ihm.analysis.ClusterStep(
'RMSD', num_models_begin, num_models_end)
1510 self.all_protocols.add_postproc(pp, state)
1513 def _add_no_postprocessing(self, num_models):
1515 state = self._last_state
1516 pp = ihm.analysis.EmptyStep()
1517 pp.num_models_begin = pp.num_models_end = num_models
1518 self.all_protocols.add_postproc(pp, state)
1521 def _add_simple_ensemble(self, pp, name, num_models, drmsd,
1522 num_models_deposited, localization_densities,
1524 """Add an ensemble generated by ad hoc methods (not using PMI).
1525 This is currently only used by the Nup84 system."""
1527 state = self._last_state
1528 group = ihm.model.ModelGroup(name=state.get_postfixed_name(name))
1529 state.add_model_group(group)
1531 self.system.locations.append(ensemble_file)
1532 e = _SimpleEnsemble(pp, group, num_models, drmsd, num_models_deposited,
1534 self.system.ensembles.append(e)
1535 for c
in state.all_modeled_components:
1536 den = localization_densities.get(c,
None)
1538 e.load_localization_density(state, c, self.asym_units[c], den)
1542 """Point a previously-created ensemble to an 'all-models' file.
1543 This could be a trajectory such as DCD, an RMF, or a multimodel
1545 self.system.locations.append(location)
1547 ind = i + self._state_ensemble_offset
1548 self.system.ensembles[ind].file = location
1550 def add_replica_exchange_analysis(self, state, rex, density_custom_ranges):
1556 protocol = self.all_protocols.get_last_protocol(state)
1557 num_models = protocol.steps[-1].num_models_end
1558 pp = _ReplicaExchangeAnalysisPostProcess(rex, num_models)
1559 pp.software = self.all_software.pmi
1560 self.all_protocols.add_postproc(pp, state)
1561 for i
in range(rex._number_of_clusters):
1562 group = ihm.model.ModelGroup(name=state.get_prefixed_name(
1563 'cluster %d' % (i + 1)))
1564 state.add_model_group(group)
1566 e = _ReplicaExchangeAnalysisEnsemble(pp, i, group, 1)
1567 self.system.ensembles.append(e)
1569 for fname, stuple
in sorted(density_custom_ranges.items()):
1570 e.load_localization_density(state, fname, stuple,
1572 for stats
in e.load_all_models(self, state):
1573 m = self.add_model(group)
1576 m.name =
'Best scoring model'
1579 for c
in state.all_modeled_components:
1582 def _get_subassembly(self, comps, name, description):
1583 """Get an Assembly consisting of the given components.
1584 `compdict` is a dictionary of the components to add, where keys
1585 are the component names and values are the sequence ranges (or
1586 None to use all residues in the component)."""
1588 for comp, seqrng
in comps.items():
1589 a = self.asym_units[comp]
1590 asyms.append(a
if seqrng
is None else a(*seqrng))
1592 a = ihm.Assembly(asyms, name=name, description=description)
1595 def _add_foxs_restraint(self, model, comp, seqrange, dataset, rg, chi,
1597 """Add a basic FoXS fit. This is largely intended for use from the
1599 assembly = self._get_subassembly(
1601 name=
"SAXS subassembly",
1602 description=
"All components that fit SAXS data")
1603 r = ihm.restraint.SASRestraint(
1604 dataset, assembly, segment=
False,
1605 fitting_method=
'FoXS', fitting_atom_type=
'Heavy atoms',
1606 multi_state=
False, radius_of_gyration=rg, details=details)
1607 r.fits[model] = ihm.restraint.SASRestraintFit(chi_value=chi)
1608 self.system.restraints.append(r)
1609 self._add_restraint_dataset(r)
1611 def add_em2d_restraint(self, state, r, i, resolution, pixel_size,
1612 image_resolution, projection_number,
1613 micrographs_number):
1614 r = _EM2DRestraint(state, r, i, resolution, pixel_size,
1615 image_resolution, projection_number,
1617 self.system.restraints.append(r)
1618 self._add_restraint_dataset(r)
1620 def add_em3d_restraint(self, state, target_ps, densities, pmi_restraint):
1622 r = _EM3DRestraint(self, state, pmi_restraint, target_ps, densities)
1623 self.system.restraints.append(r)
1624 self._add_restraint_dataset(r)
1626 def add_zaxial_restraint(self, state, ps, lower_bound, upper_bound,
1627 sigma, pmi_restraint):
1628 self._add_geometric_restraint(state, ps, lower_bound, upper_bound,
1629 sigma, pmi_restraint, self._xy_plane)
1631 def add_yaxial_restraint(self, state, ps, lower_bound, upper_bound,
1632 sigma, pmi_restraint):
1633 self._add_geometric_restraint(state, ps, lower_bound, upper_bound,
1634 sigma, pmi_restraint, self._xz_plane)
1636 def add_xyradial_restraint(self, state, ps, lower_bound, upper_bound,
1637 sigma, pmi_restraint):
1638 self._add_geometric_restraint(state, ps, lower_bound, upper_bound,
1639 sigma, pmi_restraint, self._z_axis)
1641 def _add_geometric_restraint(self, state, ps, lower_bound, upper_bound,
1642 sigma, pmi_restraint, geom):
1643 asym = get_asym_mapper_for_state(self, state, self.__asym_states)
1644 r = _GeometricRestraint(
1645 self, state, pmi_restraint, geom, asym.get_feature(ps),
1646 ihm.restraint.LowerUpperBoundDistanceRestraint(lower_bound,
1649 self.system.restraints.append(r)
1650 self._add_restraint_dataset(r)
1652 def _get_membrane(self, tor_R, tor_r, tor_th):
1653 """Get an object representing a half-torus membrane"""
1654 if not hasattr(self,
'_seen_membranes'):
1655 self._seen_membranes = {}
1658 membrane_id = tuple(int(x * 100.)
for x
in (tor_R, tor_r, tor_th))
1659 if membrane_id
not in self._seen_membranes:
1660 m = ihm.geometry.HalfTorus(
1661 center=self._center_origin,
1662 transformation=self._identity_transform,
1663 major_radius=tor_R, minor_radius=tor_r, thickness=tor_th,
1664 inner=
True, name=
'Membrane')
1665 self._seen_membranes[membrane_id] = m
1666 return self._seen_membranes[membrane_id]
1668 def add_membrane_surface_location_restraint(
1669 self, state, ps, tor_R, tor_r, tor_th, sigma, pmi_restraint):
1670 self._add_membrane_restraint(
1671 state, ps, tor_R, tor_r, tor_th, sigma, pmi_restraint,
1672 ihm.restraint.UpperBoundDistanceRestraint(0.))
1674 def add_membrane_exclusion_restraint(
1675 self, state, ps, tor_R, tor_r, tor_th, sigma, pmi_restraint):
1676 self._add_membrane_restraint(
1677 state, ps, tor_R, tor_r, tor_th, sigma, pmi_restraint,
1678 ihm.restraint.LowerBoundDistanceRestraint(0.))
1680 def _add_membrane_restraint(self, state, ps, tor_R, tor_r, tor_th,
1681 sigma, pmi_restraint, rsr):
1682 asym = get_asym_mapper_for_state(self, state, self.__asym_states)
1683 r = _GeometricRestraint(
1684 self, state, pmi_restraint,
1685 self._get_membrane(tor_R, tor_r, tor_th), asym.get_feature(ps),
1687 self.system.restraints.append(r)
1688 self._add_restraint_dataset(r)
1690 def add_model(self, group, assembly=None, representation=None):
1691 state = self._last_state
1692 if representation
is None:
1693 representation = self.default_representation
1694 protocol = self.all_protocols.get_last_protocol(state)
1695 m = _Model(state.prot, self, protocol,
1696 assembly
if assembly
else state.modeled_assembly,
1703 """Get custom python-ihm dumpers for writing PMI to from mmCIF.
1704 This returns a list of custom dumpers that can be passed as all or
1705 part of the `dumpers` argument to ihm.dumper.write(). They add
1706 PMI-specific information to mmCIF or BinaryCIF files written out
1708 return [_ReplicaExchangeProtocolDumper]
1712 """Get custom python-ihm handlers for reading PMI data from mmCIF.
1713 This returns a list of custom handlers that can be passed as all or
1714 part of the `handlers` argument to ihm.reader.read(). They read
1715 PMI-specific information from mmCIF or BinaryCIF files read in
1717 return [_ReplicaExchangeProtocolHandler]
1721 """Extract metadata from an EM density GMM file."""
1724 """Extract metadata from `filename`.
1725 @return a dict with key `dataset` pointing to the GMM file and
1726 `number_of_gaussians` to the number of GMMs (or None)"""
1727 loc = ihm.location.InputFileLocation(
1729 details=
"Electron microscopy density map, "
1730 "represented as a Gaussian Mixture Model (GMM)")
1733 loc._allow_duplicates =
True
1734 d = ihm.dataset.EMDensityDataset(loc)
1735 ret = {
'dataset': d,
'number_of_gaussians':
None}
1737 with open(filename)
as fh:
1739 if line.startswith(
'# data_fn: '):
1740 p = ihm.metadata.MRCParser()
1741 fn = line[11:].rstrip(
'\r\n')
1742 dataset = p.parse_file(os.path.join(
1743 os.path.dirname(filename), fn))[
'dataset']
1744 ret[
'dataset'].parents.append(dataset)
1745 elif line.startswith(
'# ncenters: '):
1746 ret[
'number_of_gaussians'] = int(line[12:])
Select non water and non hydrogen atoms.
def get_handlers
Get custom python-ihm handlers for reading PMI data from mmCIF.
def create_representation
Create a new Representation and return it.
def create_transformed_component
Make a new component that's a transformed copy of another.
static bool get_is_setup(const IMP::ParticleAdaptor &p)
def exclude_coordinates
Don't record coordinates for the given domain.
A decorator to associate a particle with a part of a protein/DNA/RNA.
Class to encode a modeling protocol as mmCIF.
static bool get_is_setup(const IMP::ParticleAdaptor &p)
def parse_file
Extract metadata from filename.
def pmi_range
Return a range of IHM residues indexed using PMI numbering.
Extract metadata from an EM density GMM file.
static bool get_is_setup(const IMP::ParticleAdaptor &p)
def pmi_residue
Return a single IHM residue indexed using PMI numbering.
void read_pdb(TextInput input, int model, Hierarchy h)
A single asymmetric unit in the system.
A single entity in the system.
def set_ensemble_file
Point a previously-created ensemble to an 'all-models' file.
Classes to represent data structures used in mmCIF.
def pmi_range
Return a range of IHM residues indexed using PMI numbering.
void add_restraint(RMF::FileHandle fh, Restraint *hs)
static bool get_is_setup(Model *m, ParticleIndex pi)
Ints get_index(const ParticlesTemp &particles, const Subset &subset, const Subsets &excluded)
def finalize
Do any final processing on the class hierarchy.
Base class for capturing a modeling protocol.
Basic utilities for handling cryo-electron microscopy 3D density maps.
void load_frame(RMF::FileConstHandle file, RMF::FrameID frame)
Load the given RMF frame into the state of the linked objects.
def get_dumpers
Get custom python-ihm dumpers for writing PMI to from mmCIF.
Class for easy writing of PDBs, RMFs, and stat files.
Transformation3D get_identity_transformation_3d()
Return a transformation that does not do anything.
Classes for writing output files and processing them.
A decorator for a residue.
Basic functionality that is expected to be used by a wide variety of IMP users.
def pmi_residue
Return a single IHM residue indexed using PMI numbering.
General purpose algebraic and geometric methods that are expected to be used by a wide variety of IMP...
Mapping between FASTA one-letter codes and residue types.
void link_hierarchies(RMF::FileConstHandle fh, const atom::Hierarchies &hs)
Functionality for loading, creating, manipulating and scoring atomic structures.
Hierarchies get_leaves(const Selection &h)
Select hierarchy particles identified by the biological name.
Select all ATOM and HETATM records with the given chain ids.
Inferential scoring building on methods developed as part of the Inferential Structure Determination ...