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-IHM](https://pdb-ihm.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.
39 import ihm.representation
41 import ihm.cross_linkers
45 def _assign_id(obj, seen_objs, obj_by_id):
46 """Assign a unique ID to obj, and track all ids in obj_by_id."""
47 if obj
not in seen_objs:
48 if not hasattr(obj,
'id'):
50 obj.id = len(obj_by_id)
51 seen_objs[obj] = obj.id
53 obj.id = seen_objs[obj]
56 def _get_by_residue(p):
57 """Determine whether the given particle represents a specific residue
58 or a more coarse-grained object."""
62 class _ComponentMapper:
63 """Map a Particle to a component name"""
64 def __init__(self, prot):
67 self.name =
'cif-output'
68 self.o.dictionary_pdbs[self.name] = self.prot
69 self.o._init_dictchain(self.name, self.prot,
70 multichar_chain=
True, mmcif=
True)
72 def __getitem__(self, p):
73 protname, is_a_bead = self.o.get_prot_name_from_particle(self.name, p)
78 """Map a Particle to an asym_unit"""
79 def __init__(self, simo, prot):
81 self._cm = _ComponentMapper(prot)
82 self._seen_ranges = {}
84 def __getitem__(self, p):
85 protname = self._cm[p]
86 return self.simo.asym_units[protname]
88 def get_feature(self, ps):
89 """Get an ihm.restraint.Feature that covers the given particles"""
97 rng = asym(rind, rind)
101 rng = asym(rinds[0], rinds[-1])
103 raise ValueError(
"Unsupported particle type %s" % str(p))
105 if len(rngs) > 0
and rngs[-1].asym == asym \
106 and rngs[-1].seq_id_range[1] == rng.seq_id_range[0] - 1:
107 rngs[-1].seq_id_range = (rngs[-1].seq_id_range[0],
114 if hrngs
in self._seen_ranges:
115 return self._seen_ranges[hrngs]
117 feat = ihm.restraint.ResidueFeature(rngs)
118 self._seen_ranges[hrngs] = feat
123 def __init__(self, system):
125 self.modeller_used = self.phyre2_used =
False
126 self.pmi = ihm.Software(
127 name=
"IMP PMI module",
128 version=IMP.pmi.__version__,
129 classification=
"integrative model building",
130 description=
"integrative model building",
131 location=
'https://integrativemodeling.org')
132 self.imp = ihm.Software(
133 name=
"Integrative Modeling Platform (IMP)",
134 version=IMP.__version__,
135 classification=
"integrative model building",
136 description=
"integrative model building",
137 location=
'https://integrativemodeling.org')
140 if hasattr(self.imp,
'citation'):
141 javi =
'Vel\u00e1zquez-Muriel J'
142 self.imp.citation = ihm.Citation(
144 title=
'Putting the pieces together: integrative modeling '
145 'platform software for structure determination of '
146 'macromolecular assemblies',
147 journal=
'PLoS Biol', volume=10, page_range=
'e1001244',
149 authors=[
'Russel D',
'Lasker K',
'Webb B', javi,
'Tjioe E',
150 'Schneidman-Duhovny D',
'Peterson B',
'Sali A'],
151 doi=
'10.1371/journal.pbio.1001244')
152 self.pmi.citation = ihm.Citation(
154 title=
'Modeling Biological Complexes Using Integrative '
155 'Modeling Platform.',
156 journal=
'Methods Mol Biol', volume=2022, page_range=(353, 377),
158 authors=[
'Saltzberg D',
'Greenberg CH',
'Viswanath S',
159 'Chemmama I',
'Webb B',
'Pellarin R',
'Echeverria I',
161 doi=
'10.1007/978-1-4939-9608-7_15')
162 self.system.software.extend([self.pmi, self.imp])
164 def set_modeller_used(self, version, date):
165 if self.modeller_used:
167 self.modeller_used =
True
169 name=
'MODELLER', classification=
'comparative modeling',
170 description=
'Comparative modeling by satisfaction '
171 'of spatial restraints, build ' + date,
172 location=
'https://salilab.org/modeller/', version=version)
173 self.system.software.append(s)
174 if hasattr(s,
'citation'):
175 s.citation = ihm.Citation(
177 title=
'Comparative protein modelling by satisfaction of '
178 'spatial restraints.',
179 journal=
'J Mol Biol', volume=234, page_range=(779, 815),
180 year=1993, authors=[
'Sali A',
'Blundell TL'],
181 doi=
'10.1006/jmbi.1993.1626')
183 def set_phyre2_used(self):
186 self.phyre2_used =
True
188 name=
'Phyre2', classification=
'protein homology modeling',
189 description=
'Protein Homology/analogY Recognition Engine V 2.0',
190 version=
'2.0', location=
'http://www.sbg.bio.ic.ac.uk/~phyre2/')
191 if hasattr(s,
'citation'):
192 s.citation = ihm.Citation(
194 title=
'The Phyre2 web portal for protein modeling, '
195 'prediction and analysis.',
196 journal=
'Nat Protoc', volume=10, page_range=(845, 858),
197 authors=[
'Kelley LA',
'Mezulis S',
'Yates CM',
'Wass MN',
199 year=2015, doi=
'10.1038/nprot.2015.053')
200 self.system.software.append(s)
203 def _get_fragment_is_rigid(fragment):
204 """Determine whether a fragment is modeled rigidly"""
210 class _PDBFragment(ihm.representation.ResidueSegment):
211 """Record details about part of a PDB file used as input
213 def __init__(self, state, component, start, end, pdb_offset,
214 pdbname, chain, hier, asym_unit):
217 asym_unit=asym_unit.pmi_range(start, end),
218 rigid=
None, primitive=
'sphere')
219 self.component, self.start, self.end, self.offset, self.pdbname \
220 = component, start, end, pdb_offset, pdbname
221 self.state, self.chain, self.hier = state, chain, hier
225 if pdbname.endswith(
'.cif'):
226 read_file = IMP.atom.read_mmcif
227 elif pdbname.endswith(
'.bcif'):
228 read_file = IMP.atom.read_bcif
230 read_file = IMP.atom.read_pdb
231 self.starting_hier = read_file(pdbname, state.model, sel)
233 rigid = property(
lambda self: _get_fragment_is_rigid(self),
234 lambda self, val:
None)
236 def combine(self, other):
240 class _BeadsFragment(ihm.representation.FeatureSegment):
241 """Record details about beads used to represent part of a component."""
244 def __init__(self, state, component, start, end, count, hier, asym_unit):
246 asym_unit=asym_unit(start, end), rigid=
None, primitive=
'sphere',
248 self.state, self.component, self.hier = state, component, hier
250 rigid = property(
lambda self: _get_fragment_is_rigid(self),
251 lambda self, val:
None)
253 def combine(self, other):
255 if (type(other) == type(self)
and
256 other.asym_unit.seq_id_range[0]
257 == self.asym_unit.seq_id_range[1] + 1):
258 self.asym_unit.seq_id_range = (self.asym_unit.seq_id_range[0],
259 other.asym_unit.seq_id_range[1])
260 self.count += other.count
264 class _AllModelRepresentations:
265 def __init__(self, simo):
269 self.fragments = OrderedDict()
270 self._all_representations = {}
272 def copy_component(self, state, name, original, asym_unit):
273 """Copy all representation for `original` in `state` to `name`"""
276 newf.asym_unit = asym_unit(*f.asym_unit.seq_id_range)
278 for rep
in self.fragments:
279 if original
in self.fragments[rep]:
280 if name
not in self.fragments[rep]:
281 self.fragments[rep][name] = OrderedDict()
282 self.fragments[rep][name][state] = [
283 copy_frag(f)
for f
in self.fragments[rep][original][state]]
286 first_state = list(self.fragments[rep][name].keys())[0]
287 if state
is first_state:
288 representation = self._all_representations[rep]
289 representation.extend(self.fragments[rep][name][state])
291 def add_fragment(self, state, representation, fragment):
292 """Add a model fragment."""
293 comp = fragment.component
294 id_rep = id(representation)
295 self._all_representations[id_rep] = representation
296 if id_rep
not in self.fragments:
297 self.fragments[id_rep] = OrderedDict()
298 if comp
not in self.fragments[id_rep]:
299 self.fragments[id_rep][comp] = OrderedDict()
300 if state
not in self.fragments[id_rep][comp]:
301 self.fragments[id_rep][comp][state] = []
302 fragments = self.fragments[id_rep][comp][state]
303 if len(fragments) == 0
or not fragments[-1].combine(fragment):
304 fragments.append(fragment)
307 first_state = list(self.fragments[id_rep][comp].keys())[0]
308 if state
is first_state:
309 representation.append(fragment)
313 """Track all datasets generated by PMI and add them to the ihm.System"""
314 def __init__(self, system):
317 self._datasets_by_state = {}
318 self._restraints_by_state = {}
320 def get_all_group(self, state):
321 """Get a DatasetGroup encompassing all datasets so far in this state"""
325 g = ihm.dataset.DatasetGroup(
326 self._datasets_by_state.get(state, [])
327 + [r.dataset
for r
in self._restraints_by_state.get(state, [])
331 def add(self, state, dataset):
332 """Add a new dataset."""
333 self._datasets.append(dataset)
334 if state
not in self._datasets_by_state:
335 self._datasets_by_state[state] = []
336 self._datasets_by_state[state].append(dataset)
339 self.system.orphan_datasets.append(dataset)
343 """Add the dataset for a restraint"""
344 if state
not in self._restraints_by_state:
345 self._restraints_by_state[state] = []
346 self._restraints_by_state[state].append(restraint)
349 class _CrossLinkRestraint(ihm.restraint.CrossLinkRestraint):
350 """Restrain to a set of cross-links"""
353 _label_map = {
'wtDSS':
'DSS',
'scDSS':
'DSS',
'scEDC':
'EDC'}
354 _descriptor_map = {
'DSS': ihm.cross_linkers.dss,
355 'EDC': ihm.cross_linkers.edc}
357 def __init__(self, pmi_restraint):
358 self.pmi_restraint = pmi_restraint
361 linker = getattr(self.pmi_restraint,
'linker',
None)
362 label = self.pmi_restraint.label
365 dataset=self.pmi_restraint.dataset,
366 linker=linker
or self._get_chem_descriptor(label))
369 def _get_chem_descriptor(cls, label):
371 label = cls._label_map.get(label, label)
372 if label
not in cls._descriptor_map:
376 d = ihm.ChemDescriptor(label)
377 cls._descriptor_map[label] = d
378 return cls._descriptor_map[label]
380 def _set_psi_sigma(self, model):
383 if model.m != self.pmi_restraint.model:
385 for resolution
in self.pmi_restraint.sigma_dictionary:
386 statname =
'ISDCrossLinkMS_Sigma_%s_%s' % (resolution, self.label)
387 if model.stats
and statname
in model.stats:
388 sigma = float(model.stats[statname])
389 p = self.pmi_restraint.sigma_dictionary[resolution][0]
390 old_values.append((p, p.get_scale()))
392 for psiindex
in self.pmi_restraint.psi_dictionary:
393 statname =
'ISDCrossLinkMS_Psi_%s_%s' % (psiindex, self.label)
394 if model.stats
and statname
in model.stats:
395 psi = float(model.stats[statname])
396 p = self.pmi_restraint.psi_dictionary[psiindex][0]
397 old_values.append((p, p.get_scale()))
401 return list(reversed(old_values))
403 def add_fits_from_model_statfile(self, model):
405 old_values = self._set_psi_sigma(model)
409 for xl
in self.cross_links:
411 xl.fits[model] = ihm.restraint.CrossLinkFit(
412 psi=xl.psi, sigma1=xl.sigma1, sigma2=xl.sigma2)
414 for p, scale
in old_values:
418 def __set_dataset(self, val):
419 self.pmi_restraint.dataset = val
420 dataset = property(
lambda self: self.pmi_restraint.dataset,
424 def get_asym_mapper_for_state(simo, state, asym_map):
425 asym = asym_map.get(state,
None)
427 asym = _AsymMapper(simo, state.prot)
428 asym_map[state] = asym
435 psi = property(
lambda self: self.psi_p.get_scale(),
436 lambda self, val:
None)
437 sigma1 = property(
lambda self: self.sigma1_p.get_scale(),
438 lambda self, val:
None)
439 sigma2 = property(
lambda self: self.sigma2_p.get_scale(),
440 lambda self, val:
None)
443 class _ResidueCrossLink(ihm.restraint.ResidueCrossLink, _PMICrossLink):
447 class _FeatureCrossLink(ihm.restraint.FeatureCrossLink, _PMICrossLink):
451 class _EM2DRestraint(ihm.restraint.EM2DRestraint):
452 def __init__(self, state, pmi_restraint, image_number, resolution,
453 pixel_size, image_resolution, projection_number,
455 self.pmi_restraint, self.image_number = pmi_restraint, image_number
457 dataset=pmi_restraint.datasets[image_number],
458 assembly=state.modeled_assembly,
459 segment=
False, number_raw_micrographs=micrographs_number,
460 pixel_size_width=pixel_size, pixel_size_height=pixel_size,
461 image_resolution=image_resolution,
462 number_of_projections=projection_number)
465 def __get_dataset(self):
466 return self.pmi_restraint.datasets[self.image_number]
468 def __set_dataset(self, val):
469 self.pmi_restraint.datasets[self.image_number] = val
471 dataset = property(__get_dataset, __set_dataset)
473 def add_fits_from_model_statfile(self, model):
474 ccc = self._get_cross_correlation(model)
475 transform = self._get_transformation(model)
476 rot = transform.get_rotation()
477 rm = [[e
for e
in rot.get_rotation_matrix_row(i)]
for i
in range(3)]
478 self.fits[model] = ihm.restraint.EM2DRestraintFit(
479 cross_correlation_coefficient=ccc,
481 tr_vector=transform.get_translation())
483 def _get_transformation(self, model):
484 """Get the transformation that places the model on the image"""
485 stats = model.em2d_stats
or model.stats
486 prefix =
'ElectronMicroscopy2D_%s_Image%d' % (self.pmi_restraint.label,
487 self.image_number + 1)
488 r = [float(stats[prefix +
'_Rotation%d' % i])
for i
in range(4)]
489 t = [float(stats[prefix +
'_Translation%d' % i])
493 inv = model.transform.get_inverse()
495 IMP.algebra.Vector3D(*t)) * inv
497 def _get_cross_correlation(self, model):
498 """Get the cross correlation coefficient between the model projection
500 stats = model.em2d_stats
or model.stats
501 return float(stats[
'ElectronMicroscopy2D_%s_Image%d_CCC'
502 % (self.pmi_restraint.label,
503 self.image_number + 1)])
506 class _EM3DRestraint(ihm.restraint.EM3DRestraint):
508 def __init__(self, simo, state, pmi_restraint, target_ps, densities):
509 self.pmi_restraint = pmi_restraint
511 dataset=pmi_restraint.dataset,
512 assembly=self._get_assembly(densities, simo, state),
513 fitting_method=
'Gaussian mixture models',
514 number_of_gaussians=len(target_ps))
517 def __set_dataset(self, val):
518 self.pmi_restraint.dataset = val
519 dataset = property(
lambda self: self.pmi_restraint.dataset,
522 def _get_assembly(self, densities, simo, state):
523 """Get the Assembly that this restraint acts on"""
524 cm = _ComponentMapper(state.prot)
527 components[cm[d]] =
None
528 a = simo._get_subassembly(
529 components, name=
"EM subassembly",
530 description=
"All components that fit the EM map")
533 def add_fits_from_model_statfile(self, model):
534 ccc = self._get_cross_correlation(model)
535 self.fits[model] = ihm.restraint.EM3DRestraintFit(
536 cross_correlation_coefficient=ccc)
538 def _get_cross_correlation(self, model):
539 """Get the cross correlation coefficient between the model
541 if model.stats
is not None:
542 return float(model.stats[
'GaussianEMRestraint_%s_CCC'
543 % self.pmi_restraint.label])
546 class _GeometricRestraint(ihm.restraint.GeometricRestraint):
548 def __init__(self, simo, state, pmi_restraint, geometric_object,
549 feature, distance, sigma):
550 self.pmi_restraint = pmi_restraint
552 dataset=pmi_restraint.dataset,
553 geometric_object=geometric_object, feature=feature,
554 distance=distance, harmonic_force_constant=1. / sigma,
558 def __set_dataset(self, val):
559 self.pmi_restraint.dataset = val
560 dataset = property(
lambda self: self.pmi_restraint.dataset,
564 class _ReplicaExchangeProtocolStep(ihm.protocol.Step):
565 def __init__(self, state, rex):
566 if rex.monte_carlo_sample_objects
is not None:
567 method =
'Replica exchange monte carlo'
569 method =
'Replica exchange molecular dynamics'
570 self.monte_carlo_temperature = rex.vars[
'monte_carlo_temperature']
571 self.replica_exchange_minimum_temperature = \
572 rex.vars[
'replica_exchange_minimum_temperature']
573 self.replica_exchange_maximum_temperature = \
574 rex.vars[
'replica_exchange_maximum_temperature']
576 assembly=state.modeled_assembly,
578 method=method, name=
'Sampling',
579 num_models_begin=
None,
580 num_models_end=rex.vars[
"number_of_frames"],
581 multi_scale=
True, multi_state=
False, ordered=
False, ensemble=
True)
584 class _ReplicaExchangeProtocolDumper(ihm.dumper.Dumper):
585 """Write IMP-specific information about replica exchange to mmCIF.
586 Note that IDs will have already been assigned by python-ihm's
587 standard modeling protocol dumper."""
588 def dump(self, system, writer):
589 with writer.loop(
"_imp_replica_exchange_protocol",
590 [
"protocol_id",
"step_id",
"monte_carlo_temperature",
591 "replica_exchange_minimum_temperature",
592 "replica_exchange_maximum_temperature"])
as lp:
593 for p
in system._all_protocols():
595 if isinstance(s, _ReplicaExchangeProtocolStep):
596 self._dump_step(p, s, lp)
598 def _dump_step(self, p, s, lp):
599 mintemp = s.replica_exchange_minimum_temperature
600 maxtemp = s.replica_exchange_maximum_temperature
601 lp.write(protocol_id=p._id, step_id=s._id,
602 monte_carlo_temperature=s.monte_carlo_temperature,
603 replica_exchange_minimum_temperature=mintemp,
604 replica_exchange_maximum_temperature=maxtemp)
607 class _ReplicaExchangeProtocolHandler(ihm.reader.Handler):
608 category =
'_imp_replica_exchange_protocol'
610 """Read IMP-specific information about replica exchange from mmCIF."""
611 def __call__(self, protocol_id, step_id, monte_carlo_temperature,
612 replica_exchange_minimum_temperature,
613 replica_exchange_maximum_temperature):
614 p = self.sysr.protocols.get_by_id(protocol_id)
616 s = p.steps[int(step_id)-1]
618 s.__class__ = _ReplicaExchangeProtocolStep
619 s.monte_carlo_temperature = \
620 self.get_float(monte_carlo_temperature)
621 s.replica_exchange_minimum_temperature = \
622 self.get_float(replica_exchange_minimum_temperature)
623 s.replica_exchange_maximum_temperature = \
624 self.get_float(replica_exchange_maximum_temperature)
627 class _SimpleProtocolStep(ihm.protocol.Step):
628 def __init__(self, state, num_models_end, method):
630 assembly=state.modeled_assembly,
632 method=method, name=
'Sampling',
633 num_models_begin=
None,
634 num_models_end=num_models_end,
635 multi_scale=
True, multi_state=
False, ordered=
False,
640 """Represent a single chain in a Model"""
641 def __init__(self, pmi_chain_id, asym_unit):
642 self.pmi_chain_id, self.asym_unit = pmi_chain_id, asym_unit
646 def add(self, xyz, atom_type, residue_type, residue_index,
647 all_indexes, radius):
648 if atom_type
is None:
649 self.spheres.append((xyz, residue_type, residue_index,
650 all_indexes, radius))
652 self.atoms.append((xyz, atom_type, residue_type, residue_index,
653 all_indexes, radius))
654 orig_comp = property(
lambda self: self.comp)
657 class _TransformedChain:
658 """Represent a chain that is a transformed version of another"""
659 def __init__(self, orig_chain, asym_unit, transform):
660 self.orig_chain, self.asym_unit = orig_chain, asym_unit
661 self.transform = transform
663 def __get_spheres(self):
664 for (xyz, residue_type, residue_index, all_indexes,
665 radius)
in self.orig_chain.spheres:
666 yield (self.transform * xyz, residue_type, residue_index,
668 spheres = property(__get_spheres)
670 def __get_atoms(self):
671 for (xyz, atom_type, residue_type, residue_index, all_indexes,
672 radius)
in self.orig_chain.atoms:
673 yield (self.transform * xyz, atom_type, residue_type,
674 residue_index, all_indexes, radius)
675 atoms = property(__get_atoms)
677 entity = property(
lambda self: self.orig_chain.entity)
678 orig_comp = property(
lambda self: self.orig_chain.comp)
682 def __init__(self, component, simo):
683 self._seqranges = simo._exclude_coords.get(component, [])
685 def is_excluded(self, indexes):
686 """Return True iff the given sequence range is excluded."""
687 for seqrange
in self._seqranges:
688 if indexes[0] >= seqrange[0]
and indexes[-1] <= seqrange[1]:
692 class _Model(ihm.model.Model):
693 def __init__(self, prot, simo, protocol, assembly, representation):
694 super().__init__(assembly=assembly, protocol=protocol,
695 representation=representation)
696 self.simo = weakref.proxy(simo)
702 self.em2d_stats =
None
705 self._is_restrained =
True
708 self.m = prot.get_model()
709 o.dictionary_pdbs[name] = prot
710 o._init_dictchain(name, prot, multichar_chain=
True)
711 (particle_infos_for_pdb,
712 self.geometric_center) = o.get_particle_infos_for_pdb_writing(name)
713 self.geometric_center = IMP.algebra.Vector3D(*self.geometric_center)
714 self._make_spheres_atoms(particle_infos_for_pdb, o, name, simo)
717 def all_chains(self, simo):
718 """Yield all chains, including transformed ones"""
720 for c
in self.chains:
722 chain_for_comp[c.comp] = c
723 for tc
in simo._transformed_components:
724 orig_chain = chain_for_comp.get(tc.original,
None)
726 asym = simo.asym_units[tc.name]
727 c = _TransformedChain(orig_chain, asym, tc.transform)
731 def _make_spheres_atoms(self, particle_infos_for_pdb, o, name, simo):
732 entity_for_chain = {}
735 for protname, chain_id
in o.dictchain[name].items():
736 if protname
in simo.entities:
737 entity_for_chain[chain_id] = simo.entities[protname]
740 pn = protname.split(
'.')[0]
741 entity_for_chain[chain_id] = simo.entities[pn]
742 comp_for_chain[chain_id] = protname
746 correct_asym[chain_id] = simo.asym_units[protname]
753 for (xyz, atom_type, residue_type, chain_id, residue_index,
754 all_indexes, radius)
in particle_infos_for_pdb:
755 if chain
is None or chain.pmi_chain_id != chain_id:
756 chain = _Chain(chain_id, correct_asym[chain_id])
757 chain.entity = entity_for_chain[chain_id]
758 chain.comp = comp_for_chain[chain_id]
759 self.chains.append(chain)
760 excluder = _Excluder(chain.comp, simo)
761 if not excluder.is_excluded(all_indexes
if all_indexes
762 else [residue_index]):
763 chain.add(xyz, atom_type, residue_type, residue_index,
766 def parse_rmsf_file(self, fname, component):
767 self.rmsf[component] = rmsf = {}
768 with open(str(fname))
as fh:
770 resnum, blocknum, val = line.split()
771 rmsf[int(resnum)] = (int(blocknum), float(val))
773 def get_rmsf(self, component, indexes):
774 """Get the RMSF value for the given residue indexes."""
777 rmsf = self.rmsf[component]
778 blocknums = dict.fromkeys(rmsf[ind][0]
for ind
in indexes)
779 if len(blocknums) != 1:
780 raise ValueError(
"Residue indexes %s aren't all in the same block"
782 return rmsf[indexes[0]][1]
785 for chain
in self.all_chains(self.simo):
786 pmi_offset = chain.asym_unit.entity.pmi_offset
787 for atom
in chain.atoms:
788 (xyz, atom_type, residue_type, residue_index,
789 all_indexes, radius) = atom
790 pt = self.transform * xyz
791 yield ihm.model.Atom(
792 asym_unit=chain.asym_unit,
793 seq_id=residue_index - pmi_offset,
794 atom_id=atom_type.get_string(),
796 x=pt[0], y=pt[1], z=pt[2])
798 def get_spheres(self):
799 for chain
in self.all_chains(self.simo):
800 pmi_offset = chain.asym_unit.entity.pmi_offset
801 for sphere
in chain.spheres:
802 (xyz, residue_type, residue_index,
803 all_indexes, radius) = sphere
804 if all_indexes
is None:
805 all_indexes = (residue_index,)
806 pt = self.transform * xyz
807 yield ihm.model.Sphere(
808 asym_unit=chain.asym_unit,
809 seq_id_range=(all_indexes[0] - pmi_offset,
810 all_indexes[-1] - pmi_offset),
811 x=pt[0], y=pt[1], z=pt[2], radius=radius,
812 rmsf=self.get_rmsf(chain.orig_comp, all_indexes))
816 def __init__(self, simo):
817 self.simo = weakref.proxy(simo)
819 self.protocols = OrderedDict()
821 def add_protocol(self, state):
822 """Add a new Protocol"""
823 if state
not in self.protocols:
824 self.protocols[state] = []
825 p = ihm.protocol.Protocol()
826 self.simo.system.orphan_protocols.append(p)
827 self.protocols[state].append(p)
829 def add_step(self, step, state):
830 """Add a ProtocolStep to the last Protocol of the given State"""
831 if state
not in self.protocols:
832 self.add_protocol(state)
833 protocol = self.get_last_protocol(state)
834 if len(protocol.steps) == 0:
835 step.num_models_begin = 0
837 step.num_models_begin = protocol.steps[-1].num_models_end
838 protocol.steps.append(step)
839 step.id = len(protocol.steps)
841 step.dataset_group = self.simo.all_datasets.get_all_group(state)
843 def add_postproc(self, step, state):
844 """Add a postprocessing step to the last protocol"""
845 protocol = self.get_last_protocol(state)
846 if len(protocol.analyses) == 0:
847 protocol.analyses.append(ihm.analysis.Analysis())
848 protocol.analyses[-1].steps.append(step)
850 def get_last_protocol(self, state):
851 """Return the most recently-added _Protocol"""
852 return self.protocols[state][-1]
855 class _AllStartingModels:
856 def __init__(self, simo):
860 self.models = OrderedDict()
863 def add_pdb_fragment(self, fragment):
864 """Add a starting model PDB fragment."""
865 comp = fragment.component
866 state = fragment.state
867 if comp
not in self.models:
868 self.models[comp] = OrderedDict()
869 if state
not in self.models[comp]:
870 self.models[comp][state] = []
871 models = self.models[comp][state]
872 if len(models) == 0 \
873 or models[-1].fragments[0].pdbname != fragment.pdbname:
874 model = self._add_model(fragment)
878 models[-1].fragments.append(weakref.proxy(fragment))
882 pmi_offset = models[-1].asym_unit.entity.pmi_offset
883 sid_begin = min(fragment.start - pmi_offset,
884 models[-1].asym_unit.seq_id_range[0])
885 sid_end = max(fragment.end - pmi_offset,
886 models[-1].asym_unit.seq_id_range[1])
887 models[-1].asym_unit = fragment.asym_unit.asym(sid_begin, sid_end)
888 fragment.starting_model = models[-1]
890 def _add_model(self, f):
891 if (hasattr(ihm.metadata,
'CIFParser')
892 and f.pdbname.endswith(
'.cif')):
893 parser = ihm.metadata.CIFParser()
894 elif (hasattr(ihm.metadata,
'BinaryCIFParser')
895 and f.pdbname.endswith(
'.bcif')):
896 parser = ihm.metadata.BinaryCIFParser()
898 parser = ihm.metadata.PDBParser()
899 r = parser.parse_file(f.pdbname)
901 self.simo._add_dataset(r[
'dataset'])
903 templates = r[
'templates'].get(f.chain, [])
906 self.simo.system.locations.append(t.alignment_file)
908 self.simo._add_dataset(t.dataset)
909 source = r.get(
'entity_source', {}).get(f.chain)
911 f.asym_unit.entity.source = source
912 pmi_offset = f.asym_unit.entity.pmi_offset
914 asym_unit=f.asym_unit.asym.pmi_range(f.start, f.end),
915 dataset=r[
'dataset'], asym_id=f.chain,
916 templates=templates, offset=f.offset + pmi_offset,
917 metadata=r.get(
'metadata'),
918 software=r[
'software'][0]
if r[
'software']
else None,
919 script_file=r[
'script'])
920 m.fragments = [weakref.proxy(f)]
924 class _StartingModel(ihm.startmodel.StartingModel):
925 def get_seq_dif(self):
929 pmi_offset = self.asym_unit.entity.pmi_offset
930 mh = IMP.mmcif.data._StartingModelAtomHandler(self.templates,
932 for f
in self.fragments:
935 residue_indexes=list(range(f.start - f.offset,
936 f.end - f.offset + 1)))
937 for a
in mh.get_ihm_atoms(sel.get_selected_particles(),
938 f.offset - pmi_offset):
940 self._seq_dif = mh._seq_dif
943 class _ReplicaExchangeAnalysisPostProcess(ihm.analysis.ClusterStep):
944 """Post processing using AnalysisReplicaExchange0 macro"""
946 def __init__(self, rex, num_models_begin):
949 for fname
in self.get_all_stat_files():
950 with open(str(fname))
as fh:
951 num_models_end += len(fh.readlines())
953 feature=
'RMSD', num_models_begin=num_models_begin,
954 num_models_end=num_models_end)
956 def get_stat_file(self, cluster_num):
957 return self.rex._outputdir / (
"cluster.%d" % cluster_num) /
'stat.out'
959 def get_all_stat_files(self):
960 for i
in range(self.rex._number_of_clusters):
961 yield self.get_stat_file(i)
964 class _ReplicaExchangeAnalysisEnsemble(ihm.model.Ensemble):
965 """Ensemble generated using AnalysisReplicaExchange0 macro"""
967 num_models_deposited =
None
969 def __init__(self, pp, cluster_num, model_group, num_deposit):
970 with open(str(pp.get_stat_file(cluster_num)))
as fh:
971 num_models = len(fh.readlines())
973 num_models=num_models,
974 model_group=model_group, post_process=pp,
975 clustering_feature=pp.feature,
976 name=model_group.name)
977 self.cluster_num = cluster_num
978 self.num_models_deposited = num_deposit
980 def get_rmsf_file(self, component):
981 return (self.post_process.rex._outputdir
982 / (
'cluster.%d' % self.cluster_num)
983 / (
'rmsf.%s.dat' % component))
985 def load_rmsf(self, model, component):
986 fname = self.get_rmsf_file(component)
988 model.parse_rmsf_file(fname, component)
990 def get_localization_density_file(self, fname):
991 return (self.post_process.rex._outputdir
992 / (
'cluster.%d' % self.cluster_num)
993 / (
'%s.mrc' % fname))
995 def load_localization_density(self, state, fname, select_tuple,
997 fullpath = self.get_localization_density_file(fname)
998 if fullpath.exists():
999 details =
"Localization density for %s %s" \
1000 % (fname, self.model_group.name)
1001 local_file = ihm.location.OutputFileLocation(str(fullpath),
1003 for s
in select_tuple:
1004 if isinstance(s, tuple)
and len(s) == 3:
1005 asym = asym_units[s[2]].pmi_range(s[0], s[1])
1007 asym = asym_units[s]
1008 den = ihm.model.LocalizationDensity(file=local_file,
1010 self.densities.append(den)
1012 def load_all_models(self, simo, state):
1013 stat_fname = self.post_process.get_stat_file(self.cluster_num)
1015 with open(str(stat_fname))
as fh:
1016 stats = ast.literal_eval(fh.readline())
1018 rmf_file = stat_fname.parent / (
"%d.rmf3" % model_num)
1020 if rmf_file.exists():
1021 rh = RMF.open_rmf_file_read_only(str(rmf_file))
1022 system = state._pmi_object.system
1028 if model_num >= self.num_models_deposited:
1032 def _get_precision(self):
1033 precfile = (self.post_process.rex._outputdir /
1034 (
"precision.%d.%d.out" % (self.cluster_num,
1036 if not precfile.exists():
1040 r'All .*/cluster.%d/ average centroid distance ([\d\.]+)'
1042 with open(str(precfile))
as fh:
1046 return float(m.group(1))
1048 precision = property(
lambda self: self._get_precision(),
1049 lambda self, val:
None)
1052 class _SimpleEnsemble(ihm.model.Ensemble):
1053 """Simple manually-created ensemble"""
1055 num_models_deposited =
None
1057 def __init__(self, pp, model_group, num_models, drmsd,
1058 num_models_deposited, ensemble_file):
1060 model_group=model_group, post_process=pp, num_models=num_models,
1061 file=ensemble_file, precision=drmsd, name=model_group.name,
1062 clustering_feature=
'dRMSD')
1063 self.num_models_deposited = num_models_deposited
1065 def load_localization_density(self, state, component, asym, local_file):
1066 den = ihm.model.LocalizationDensity(file=local_file, asym_unit=asym)
1067 self.densities.append(den)
1070 class _CustomDNAAlphabet:
1071 """Custom DNA alphabet that maps A,C,G,T (rather than DA,DC,DG,DT
1072 as in python-ihm)"""
1073 _comps = dict([cc.code_canonical, cc]
1074 for cc
in ihm.DNAAlphabet._comps.values())
1077 class _EntityMapper(dict):
1078 """Handle mapping from IMP components (without copy number) to CIF
1079 entities. Multiple components may map to the same entity if they
1081 def __init__(self, system):
1083 self._sequence_dict = {}
1085 self.system = system
1087 def _get_alphabet(self, alphabet):
1088 """Map a PMI alphabet to an IHM alphabet"""
1092 alphabet_map = {
None: ihm.LPeptideAlphabet,
1093 IMP.pmi.alphabets.amino_acid: ihm.LPeptideAlphabet,
1094 IMP.pmi.alphabets.rna: ihm.RNAAlphabet,
1095 IMP.pmi.alphabets.dna: _CustomDNAAlphabet}
1096 if alphabet
in alphabet_map:
1097 return alphabet_map[alphabet]
1099 raise TypeError(
"Don't know how to handle %s" % alphabet)
1101 def add(self, component_name, sequence, offset, alphabet, uniprot):
1102 def entity_seq(sequence):
1105 return [
'UNK' if s ==
'X' else s
for s
in sequence]
1108 if sequence
not in self._sequence_dict:
1111 d = component_name.split(
"@")[0].split(
".")[0]
1112 entity = Entity(entity_seq(sequence), description=d,
1114 alphabet=self._get_alphabet(alphabet),
1116 self.system.entities.append(entity)
1117 self._sequence_dict[sequence] = entity
1118 self[component_name] = self._sequence_dict[sequence]
1121 class _TransformedComponent:
1122 def __init__(self, name, original, transform):
1123 self.name, self.original, self.transform = name, original, transform
1127 """Class with similar interface to weakref.ref, but keeps a strong ref"""
1128 def __init__(self, ref):
1135 class _State(ihm.model.State):
1136 """Representation of a single state in the system."""
1138 def __init__(self, pmi_object, po):
1143 self._pmi_object = weakref.proxy(pmi_object)
1144 if hasattr(pmi_object,
'state'):
1147 self._pmi_state = _SimpleRef(pmi_object.state)
1149 self._pmi_state = weakref.ref(pmi_object)
1151 old_name = self.name
1152 super().__init__(experiment_type=
'Fraction of bulk')
1153 self.name = old_name
1157 self.modeled_assembly = ihm.Assembly(
1158 name=
"Modeled assembly",
1159 description=self.get_postfixed_name(
1160 "All components modeled by IMP"))
1161 po.system.orphan_assemblies.append(self.modeled_assembly)
1163 self.all_modeled_components = []
1166 return hash(self._pmi_state())
1168 def __eq__(self, other):
1169 return self._pmi_state() == other._pmi_state()
1171 def add_model_group(self, group):
1175 def get_prefixed_name(self, name):
1176 """Prefix the given name with the state name, if available."""
1178 return self.short_name +
' ' + name
1182 return name[0].upper() + name[1:]
if name
else ''
1184 def get_postfixed_name(self, name):
1185 """Postfix the given name with the state name, if available."""
1187 return "%s in state %s" % (name, self.short_name)
1191 short_name = property(
lambda self: self._pmi_state().short_name)
1192 long_name = property(
lambda self: self._pmi_state().long_name)
1194 def __get_name(self):
1195 return self._pmi_state().long_name
1197 def __set_name(self, val):
1198 self._pmi_state().long_name = val
1200 name = property(__get_name, __set_name)
1204 """A single entity in the system. This contains information (such as
1205 database identifiers) specific to a particular sequence rather than
1206 a copy (for example, when modeling a homodimer, two AsymUnits will
1207 point to the same Entity).
1209 This functions identically to the base ihm.Entity class, but it
1210 allows identifying residues by either the PMI numbering scheme
1211 (which is always contiguous starting at 1, covering the entire
1212 sequence in the FASTA files, or the IHM scheme (seq_id, which also
1213 starts at 1, but which only covers the modeled subset of the full
1214 sequence, with non-modeled N-terminal or C-terminal residues
1215 removed). The actual offset (which is the integer to be added to the
1216 IHM numbering to get PMI numbering, or equivalently the number of
1217 not-represented N-terminal residues in the PMI sequence) is
1218 available in the `pmi_offset` member.
1220 If a UniProt accession was provided for the sequence (either when
1221 State.create_molecule() was called, or in the FASTA alignment file
1222 header) then that is available in the `uniprot` member, and can be
1223 added to the IHM system with the add_uniprot_reference method.
1225 def __init__(self, sequence, pmi_offset, uniprot, *args, **keys):
1228 self.pmi_offset = pmi_offset
1229 self.uniprot = uniprot
1230 super().__init__(sequence, *args, **keys)
1233 """Return a single IHM residue indexed using PMI numbering"""
1234 return self.residue(res_id - self.pmi_offset)
1237 """Return a range of IHM residues indexed using PMI numbering"""
1238 off = self.pmi_offset
1239 return self(res_id_begin - off, res_id_end - off)
1242 """Add UniProt accession (if available) to the IHM system.
1243 If a UniProt accession was provided for the sequence (either when
1244 State.create_molecule() was called, or in the FASTA alignment file
1245 header), then look this up at the UniProt web site (requires
1246 network access) to get full information, and add it to the IHM
1247 system. The resulting reference object is returned. If the IMP
1248 and UniProt sequences are not identical, then this object may
1249 need to be modified by specifying an alignment and/or
1250 single-point mutations.
1253 print(
'Adding UniProt accession %s reference for entity %s'
1254 % (self.uniprot, self.description))
1255 ref = ihm.reference.UniProtSequence.from_accession(self.uniprot)
1256 self.references.append(ref)
1261 """A single asymmetric unit in the system. This roughly corresponds to
1262 a single PMI subunit (sequence, copy, or clone).
1264 This functions identically to the base ihm.AsymUnit class, but it
1265 allows identifying residues by either the PMI numbering scheme
1266 (which is always contiguous starting at 1, covering the entire
1267 sequence in the FASTA files, or the IHM scheme (seq_id, which also
1268 starts at 1, but which only covers the modeled subset of the full
1269 sequence, with non-modeled N-terminal or C-terminal residues
1272 The `entity` member of this class points to an Entity object, which
1273 contains information (such as database identifiers) specific to
1274 a particular sequence rather than a copy (for example, when modeling
1275 a homodimer, two AsymUnits will point to the same Entity).
1278 def __init__(self, entity, *args, **keys):
1280 entity, auth_seq_id_map=entity.pmi_offset, *args, **keys)
1283 """Return a single IHM residue indexed using PMI numbering"""
1284 return self.residue(res_id - self.entity.pmi_offset)
1287 """Return a range of IHM residues indexed using PMI numbering"""
1288 off = self.entity.pmi_offset
1289 return self(res_id_begin - off, res_id_end - off)
1293 """Class to encode a modeling protocol as mmCIF.
1295 IMP has basic support for writing out files in mmCIF format, for
1296 deposition in [PDB-IHM](https://pdb-ihm.org/).
1297 After creating an instance of this class, attach it to an
1298 IMP.pmi.topology.System object. After this, any
1299 generated models and metadata are automatically collected in the
1300 `system` attribute, which is an
1301 [ihm.System](https://python-ihm.readthedocs.io/en/latest/main.html#ihm.System) object.
1302 Once the protocol is complete, call finalize() to make sure `system`
1303 contains everything, then use the
1304 [python-ihm API](https://python-ihm.readthedocs.io/en/latest/dumper.html#ihm.dumper.write)
1305 to write out files in mmCIF or BinaryCIF format.
1307 Each PMI subunit will be mapped to an IHM AsymUnit class which contains the
1308 subset of the sequence that was represented. Use the `asym_units` dict to
1309 get this object given a PMI subunit name. Each unique sequence will be
1310 mapped to an IHM Entity class (for example when modeling a homodimer
1311 there will be two AsymUnits which both point to the same Entity). Use
1312 the `entities` dict to get this object from a PMI subunit name.
1314 See also Entity, AsymUnit, get_handlers(), get_dumpers().
1318 self.system = ihm.System(model_details=self._get_model_details())
1319 self._state_group = ihm.model.StateGroup()
1320 self.system.state_groups.append(self._state_group)
1322 self._state_ensemble_offset = 0
1323 self._main_script = os.path.abspath(sys.argv[0])
1326 loc = ihm.location.WorkflowFileLocation(
1327 path=self._main_script,
1328 details=
"The main integrative modeling script")
1329 self.system.locations.append(loc)
1332 self.__asym_states = {}
1333 self._working_directory = os.getcwd()
1335 "Default representation")
1336 self.entities = _EntityMapper(self.system)
1338 self.asym_units = {}
1339 self._all_components = {}
1340 self.all_modeled_components = []
1341 self._transformed_components = []
1342 self.sequence_dict = {}
1345 self._xy_plane = ihm.geometry.XYPlane()
1346 self._xz_plane = ihm.geometry.XZPlane()
1347 self._z_axis = ihm.geometry.ZAxis()
1348 self._center_origin = ihm.geometry.Center(0, 0, 0)
1349 self._identity_transform = ihm.geometry.Transformation.identity()
1352 self._exclude_coords = {}
1354 self.all_representations = _AllModelRepresentations(self)
1355 self.all_protocols = _AllProtocols(self)
1356 self.all_datasets = _AllDatasets(self.system)
1357 self.all_starting_models = _AllStartingModels(self)
1359 self.all_software = _AllSoftware(self.system)
1361 def _get_model_details(self):
1362 """Return a more detailed description of the modeling."""
1363 tm = time.strftime(
"%c")
1364 return "Generated by the Integrative Modeling Platform (IMP) on " + tm
1367 """Create a new Representation and return it. This can be
1368 passed to add_model(), add_bead_element() or add_pdb_element()."""
1369 r = ihm.representation.Representation(name=name)
1370 self.system.orphan_representations.append(r)
1374 """Don't record coordinates for the given domain.
1375 Coordinates for the given domain (specified by a component name
1376 and a 2-element tuple giving the start and end residue numbers)
1377 will be excluded from the mmCIF file. This can be used to exclude
1378 parts of the structure that weren't well resolved in modeling.
1379 Any bead or residue that lies wholly within this range will be
1380 excluded. Multiple ranges for a given component can be excluded
1381 by calling this method multiple times."""
1382 if component
not in self._exclude_coords:
1383 self._exclude_coords[component] = []
1384 self._exclude_coords[component].append(seqrange)
1386 def _is_excluded(self, component, start, end):
1387 """Return True iff this chunk of sequence should be excluded"""
1388 for seqrange
in self._exclude_coords.get(component, ()):
1389 if start >= seqrange[0]
and end <= seqrange[1]:
1392 def _add_state(self, state):
1393 """Create a new state and return a pointer to it."""
1394 self._state_ensemble_offset = len(self.system.ensembles)
1395 s = _State(state, self)
1396 self._state_group.append(s)
1397 self._last_state = s
1400 def _get_chain_for_component(self, name, output):
1401 """Get the chain ID for a component, if any."""
1403 if name
in self.asym_units:
1404 return self.asym_units[name]._id
1409 def _get_assembly_comps(self, assembly):
1410 """Get the names of the components in the given assembly"""
1414 comps[ca.details] =
None
1418 """Make a new component that's a transformed copy of another.
1419 All representation for the existing component is copied to the
1421 assembly_comps = self._get_assembly_comps(state.modeled_assembly)
1422 if name
in assembly_comps:
1423 raise ValueError(
"Component %s already exists" % name)
1424 elif original
not in assembly_comps:
1425 raise ValueError(
"Original component %s does not exist" % original)
1426 self.create_component(state, name,
True)
1427 self.add_component_sequence(state, name, self.sequence_dict[original])
1428 self._transformed_components.append(_TransformedComponent(
1429 name, original, transform))
1430 self.all_representations.copy_component(state, name, original,
1431 self.asym_units[name])
1433 def create_component(self, state, name, modeled, asym_name=None):
1434 if asym_name
is None:
1436 new_comp = name
not in self._all_components
1437 self._all_components[name] =
None
1439 state.all_modeled_components.append(name)
1440 if asym_name
not in self.asym_units:
1442 self.asym_units[asym_name] =
None
1444 self.all_modeled_components.append(name)
1446 def add_component_sequence(self, state, name, seq, asym_name=None,
1447 alphabet=
None, uniprot=
None):
1448 if asym_name
is None:
1451 if name
in self.sequence_dict:
1452 if self.sequence_dict[name] != seq:
1453 raise ValueError(
"Sequence mismatch for component %s" % name)
1455 self.sequence_dict[name] = seq
1459 self.entities.add(name, seq, 0, alphabet, uniprot)
1460 if asym_name
in self.asym_units:
1461 if self.asym_units[asym_name]
is None:
1463 entity = self.entities[name]
1464 asym =
AsymUnit(entity, details=asym_name)
1465 self.system.asym_units.append(asym)
1466 self.asym_units[asym_name] = asym
1467 state.modeled_assembly.append(self.asym_units[asym_name])
1470 """Called immediately after the PMI system is built"""
1471 for entity
in self.system.entities:
1472 _trim_unrep_termini(entity, self.system.asym_units,
1473 self.system.orphan_representations)
1475 def _add_restraint_model_fits(self):
1476 """Add fits to restraints for all known models"""
1477 for group, m
in self.system._all_models():
1478 if m._is_restrained:
1479 for r
in self.system.restraints:
1480 if hasattr(r,
'add_fits_from_model_statfile'):
1481 r.add_fits_from_model_statfile(m)
1484 """Do any final processing on the class hierarchy.
1485 After calling this method, the `system` member (an instance
1486 of `ihm.System`) completely reproduces the PMI modeling, and
1487 can be written out to an mmCIF file with `ihm.dumper.write`,
1488 and/or modified using the ihm API."""
1489 self._add_restraint_model_fits()
1491 def add_pdb_element(self, state, name, start, end, offset, pdbname,
1492 chain, hier, representation=
None):
1493 if self._is_excluded(name, start, end):
1495 if representation
is None:
1496 representation = self.default_representation
1497 asym = self.asym_units[name]
1498 p = _PDBFragment(state, name, start, end, offset, pdbname, chain,
1500 self.all_representations.add_fragment(state, representation, p)
1501 self.all_starting_models.add_pdb_fragment(p)
1503 def add_bead_element(self, state, name, start, end, num, hier,
1504 representation=
None):
1505 if self._is_excluded(name, start, end):
1507 if representation
is None:
1508 representation = self.default_representation
1509 asym = self.asym_units[name]
1510 pmi_offset = asym.entity.pmi_offset
1511 b = _BeadsFragment(state, name, start - pmi_offset, end - pmi_offset,
1513 self.all_representations.add_fragment(state, representation, b)
1515 def get_cross_link_group(self, pmi_restraint):
1516 r = _CrossLinkRestraint(pmi_restraint)
1517 self.system.restraints.append(r)
1518 self._add_restraint_dataset(r)
1521 def add_experimental_cross_link(self, r1, c1, r2, c2, rsr):
1522 if c1
not in self._all_components
or c2
not in self._all_components:
1528 e1 = self.entities[c1]
1529 e2 = self.entities[c2]
1530 xl = ihm.restraint.ExperimentalCrossLink(residue1=e1.pmi_residue(r1),
1531 residue2=e2.pmi_residue(r2))
1532 rsr.experimental_cross_links.append([xl])
1535 def add_cross_link(self, state, ex_xl, p1, p2, length, sigma1_p, sigma2_p,
1538 asym = get_asym_mapper_for_state(self, state, self.__asym_states)
1539 d = ihm.restraint.UpperBoundDistanceRestraint(length)
1541 if _get_by_residue(p1)
and _get_by_residue(p2):
1542 cls = _ResidueCrossLink
1544 cls = _FeatureCrossLink
1545 xl = cls(ex_xl, asym1=asym[p1], asym2=asym[p2], distance=d,
1548 xl.psi_p, xl.sigma1_p, xl.sigma2_p = psi_p, sigma1_p, sigma2_p
1549 rsr.cross_links.append(xl)
1551 def add_replica_exchange(self, state, rex):
1556 step = _ReplicaExchangeProtocolStep(state, rex)
1557 step.software = self.all_software.pmi
1558 self.all_protocols.add_step(step, state)
1560 def _add_simple_dynamics(self, num_models_end, method):
1562 state = self._last_state
1563 self.all_protocols.add_step(_SimpleProtocolStep(state, num_models_end,
1566 def _add_protocol(self):
1568 state = self._last_state
1569 self.all_protocols.add_protocol(state)
1571 def _add_dataset(self, dataset):
1572 return self.all_datasets.add(self._last_state, dataset)
1574 def _add_restraint_dataset(self, restraint):
1575 return self.all_datasets.add_restraint(self._last_state, restraint)
1577 def _add_simple_postprocessing(self, num_models_begin, num_models_end):
1579 state = self._last_state
1580 pp = ihm.analysis.ClusterStep(
'RMSD', num_models_begin, num_models_end)
1581 self.all_protocols.add_postproc(pp, state)
1584 def _add_no_postprocessing(self, num_models):
1586 state = self._last_state
1587 pp = ihm.analysis.EmptyStep()
1588 pp.num_models_begin = pp.num_models_end = num_models
1589 self.all_protocols.add_postproc(pp, state)
1592 def _add_simple_ensemble(self, pp, name, num_models, drmsd,
1593 num_models_deposited, localization_densities,
1595 """Add an ensemble generated by ad hoc methods (not using PMI).
1596 This is currently only used by the Nup84 system."""
1598 state = self._last_state
1599 group = ihm.model.ModelGroup(name=state.get_postfixed_name(name))
1600 state.add_model_group(group)
1602 self.system.locations.append(ensemble_file)
1603 e = _SimpleEnsemble(pp, group, num_models, drmsd, num_models_deposited,
1605 self.system.ensembles.append(e)
1606 for c
in state.all_modeled_components:
1607 den = localization_densities.get(c,
None)
1609 e.load_localization_density(state, c, self.asym_units[c], den)
1613 """Point a previously-created ensemble to an 'all-models' file.
1614 This could be a trajectory such as DCD, an RMF, or a multimodel
1616 self.system.locations.append(location)
1618 ind = i + self._state_ensemble_offset
1619 self.system.ensembles[ind].file = location
1621 def add_replica_exchange_analysis(self, state, rex, density_custom_ranges):
1627 protocol = self.all_protocols.get_last_protocol(state)
1628 num_models = protocol.steps[-1].num_models_end
1629 pp = _ReplicaExchangeAnalysisPostProcess(rex, num_models)
1630 pp.software = self.all_software.pmi
1631 self.all_protocols.add_postproc(pp, state)
1632 for i
in range(rex._number_of_clusters):
1633 group = ihm.model.ModelGroup(name=state.get_prefixed_name(
1634 'cluster %d' % (i + 1)))
1635 state.add_model_group(group)
1637 e = _ReplicaExchangeAnalysisEnsemble(pp, i, group, 1)
1638 self.system.ensembles.append(e)
1640 for fname, stuple
in sorted(density_custom_ranges.items()):
1641 e.load_localization_density(state, fname, stuple,
1643 for stats
in e.load_all_models(self, state):
1644 m = self.add_model(group)
1647 m.name =
'Best scoring model'
1650 for c
in state.all_modeled_components:
1653 def _get_subassembly(self, comps, name, description):
1654 """Get an Assembly consisting of the given components.
1655 `compdict` is a dictionary of the components to add, where keys
1656 are the component names and values are the sequence ranges (or
1657 None to use all residues in the component)."""
1659 for comp, seqrng
in comps.items():
1660 a = self.asym_units[comp]
1661 asyms.append(a
if seqrng
is None else a(*seqrng))
1663 a = ihm.Assembly(asyms, name=name, description=description)
1666 def _add_foxs_restraint(self, model, comp, seqrange, dataset, rg, chi,
1668 """Add a basic FoXS fit. This is largely intended for use from the
1670 assembly = self._get_subassembly(
1672 name=
"SAXS subassembly",
1673 description=
"All components that fit SAXS data")
1674 r = ihm.restraint.SASRestraint(
1675 dataset, assembly, segment=
False,
1676 fitting_method=
'FoXS', fitting_atom_type=
'Heavy atoms',
1677 multi_state=
False, radius_of_gyration=rg, details=details)
1678 r.fits[model] = ihm.restraint.SASRestraintFit(chi_value=chi)
1679 self.system.restraints.append(r)
1680 self._add_restraint_dataset(r)
1682 def add_em2d_restraint(self, state, r, i, resolution, pixel_size,
1683 image_resolution, projection_number,
1684 micrographs_number):
1685 r = _EM2DRestraint(state, r, i, resolution, pixel_size,
1686 image_resolution, projection_number,
1688 self.system.restraints.append(r)
1689 self._add_restraint_dataset(r)
1691 def add_em3d_restraint(self, state, target_ps, densities, pmi_restraint):
1693 r = _EM3DRestraint(self, state, pmi_restraint, target_ps, densities)
1694 self.system.restraints.append(r)
1695 self._add_restraint_dataset(r)
1697 def add_zaxial_restraint(self, state, ps, lower_bound, upper_bound,
1698 sigma, pmi_restraint):
1699 self._add_geometric_restraint(state, ps, lower_bound, upper_bound,
1700 sigma, pmi_restraint, self._xy_plane)
1702 def add_yaxial_restraint(self, state, ps, lower_bound, upper_bound,
1703 sigma, pmi_restraint):
1704 self._add_geometric_restraint(state, ps, lower_bound, upper_bound,
1705 sigma, pmi_restraint, self._xz_plane)
1707 def add_xyradial_restraint(self, state, ps, lower_bound, upper_bound,
1708 sigma, pmi_restraint):
1709 self._add_geometric_restraint(state, ps, lower_bound, upper_bound,
1710 sigma, pmi_restraint, self._z_axis)
1712 def _add_geometric_restraint(self, state, ps, lower_bound, upper_bound,
1713 sigma, pmi_restraint, geom):
1714 asym = get_asym_mapper_for_state(self, state, self.__asym_states)
1715 r = _GeometricRestraint(
1716 self, state, pmi_restraint, geom, asym.get_feature(ps),
1717 ihm.restraint.LowerUpperBoundDistanceRestraint(lower_bound,
1720 self.system.restraints.append(r)
1721 self._add_restraint_dataset(r)
1723 def _get_membrane(self, tor_R, tor_r, tor_th):
1724 """Get an object representing a half-torus membrane"""
1725 if not hasattr(self,
'_seen_membranes'):
1726 self._seen_membranes = {}
1729 membrane_id = tuple(int(x * 100.)
for x
in (tor_R, tor_r, tor_th))
1730 if membrane_id
not in self._seen_membranes:
1731 m = ihm.geometry.HalfTorus(
1732 center=self._center_origin,
1733 transformation=self._identity_transform,
1734 major_radius=tor_R, minor_radius=tor_r, thickness=tor_th,
1735 inner=
True, name=
'Membrane')
1736 self._seen_membranes[membrane_id] = m
1737 return self._seen_membranes[membrane_id]
1739 def add_membrane_surface_location_restraint(
1740 self, state, ps, tor_R, tor_r, tor_th, sigma, pmi_restraint):
1741 self._add_membrane_restraint(
1742 state, ps, tor_R, tor_r, tor_th, sigma, pmi_restraint,
1743 ihm.restraint.UpperBoundDistanceRestraint(0.))
1745 def add_membrane_exclusion_restraint(
1746 self, state, ps, tor_R, tor_r, tor_th, sigma, pmi_restraint):
1747 self._add_membrane_restraint(
1748 state, ps, tor_R, tor_r, tor_th, sigma, pmi_restraint,
1749 ihm.restraint.LowerBoundDistanceRestraint(0.))
1751 def _add_membrane_restraint(self, state, ps, tor_R, tor_r, tor_th,
1752 sigma, pmi_restraint, rsr):
1753 asym = get_asym_mapper_for_state(self, state, self.__asym_states)
1754 r = _GeometricRestraint(
1755 self, state, pmi_restraint,
1756 self._get_membrane(tor_R, tor_r, tor_th), asym.get_feature(ps),
1758 self.system.restraints.append(r)
1759 self._add_restraint_dataset(r)
1761 def add_model(self, group, assembly=None, representation=None):
1762 state = self._last_state
1763 if representation
is None:
1764 representation = self.default_representation
1765 protocol = self.all_protocols.get_last_protocol(state)
1766 m = _Model(state.prot, self, protocol,
1767 assembly
if assembly
else state.modeled_assembly,
1774 """Get custom python-ihm dumpers for writing PMI to from mmCIF.
1775 This returns a list of custom dumpers that can be passed as all or
1776 part of the `dumpers` argument to ihm.dumper.write(). They add
1777 PMI-specific information to mmCIF or BinaryCIF files written out
1779 return [_ReplicaExchangeProtocolDumper]
1783 """Get custom python-ihm handlers for reading PMI data from mmCIF.
1784 This returns a list of custom handlers that can be passed as all or
1785 part of the `handlers` argument to ihm.reader.read(). They read
1786 PMI-specific information from mmCIF or BinaryCIF files read in
1788 return [_ReplicaExchangeProtocolHandler]
1792 """Extract metadata from an EM density GMM file."""
1795 """Extract metadata from `filename`.
1796 @return a dict with key `dataset` pointing to the GMM file and
1797 `number_of_gaussians` to the number of GMMs (or None)"""
1798 loc = ihm.location.InputFileLocation(
1800 details=
"Electron microscopy density map, "
1801 "represented as a Gaussian Mixture Model (GMM)")
1804 loc._allow_duplicates =
True
1805 d = ihm.dataset.EMDensityDataset(loc)
1806 ret = {
'dataset': d,
'number_of_gaussians':
None}
1808 with open(filename)
as fh:
1810 if line.startswith(
'# data_fn: '):
1811 p = ihm.metadata.MRCParser()
1812 fn = line[11:].rstrip(
'\r\n')
1813 dataset = p.parse_file(os.path.join(
1814 os.path.dirname(filename), fn))[
'dataset']
1815 ret[
'dataset'].parents.append(dataset)
1816 elif line.startswith(
'# ncenters: '):
1817 ret[
'number_of_gaussians'] = int(line[12:])
1821 def _trim_unrep_termini(entity, asyms, representations):
1822 """Trim Entity sequence to only cover represented residues.
1824 PDB policy is for amino acid Entity sequences to be polymers (so
1825 they should include any loops or other gaps in the midst of the
1826 sequence) but for the termini to be trimmed of any not-modeled
1827 residues. Here, we modify the Entity sequence to remove any parts
1828 that are not included in any representation. This may change the
1829 numbering if any N-terminal residues are removed, and thus the offset
1830 between PMI and IHM numbering, as both count from 1."""
1832 for rep
in representations:
1834 if seg.asym_unit.entity
is entity:
1835 seg_range = seg.asym_unit.seq_id_range
1836 if rep_range
is None:
1837 rep_range = list(seg_range)
1839 rep_range[0] = min(rep_range[0], seg_range[0])
1840 rep_range[1] = max(rep_range[1], seg_range[1])
1842 if rep_range
is None or rep_range == [1, len(entity.sequence)]:
1848 pmi_offset = int(rep_range[0]) - 1
1849 entity.pmi_offset = pmi_offset
1851 if asym.entity
is entity:
1852 asym.auth_seq_id_map = entity.pmi_offset
1854 entity.sequence = entity.sequence[rep_range[0] - 1:rep_range[1]]
1860 for rep
in representations:
1862 if seg.asym_unit.entity
is entity:
1863 seg_range = seg.asym_unit.seq_id_range
1864 seg.asym_unit.seq_id_range = (seg_range[0] - pmi_offset,
1865 seg_range[1] - pmi_offset)
1866 if seg.starting_model:
1867 model = seg.starting_model
1870 if id(model)
in seen_models:
1872 seen_models.add(id(model))
1873 seg_range = model.asym_unit.seq_id_range
1874 model.asym_unit.seq_id_range = \
1875 (seg_range[0] - pmi_offset,
1876 seg_range[1] - pmi_offset)
1877 model.offset = model.offset - pmi_offset
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.
def add_uniprot_reference
Add UniProt accession (if available) to the IHM system.
def finalize_build
Called immediately after the PMI system is built.
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.
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 ...