1 """@namespace IMP.mmcif.data
2 @brief Classes to represent data structures used in mmCIF.
23 def _fill_imp_to_ihm():
24 d = dict(x
for x
in inspect.getmembers(
IMP.atom)
27 for comp
in ihm.LPeptideAlphabet._comps.values():
29 _imp_to_ihm[d[comp.id]] = comp
31 alpha = ihm.RNAAlphabet()
32 for code
in [
'ADE',
'CYT',
'GUA',
'URA']:
33 _imp_to_ihm[d[code]] = alpha[d[code].get_string()]
34 alpha = ihm.DNAAlphabet()
35 for code
in [
'DADE',
'DCYT',
'DGUA',
'DTHY']:
36 _imp_to_ihm[d[code]] = alpha[d[code].get_string()]
38 _imp_to_ihm[
None] =
None
45 """Given a Hierarchy, walk up and find the parent Molecule"""
53 def _check_sequential(fragment, resinds):
54 for i
in range(1, len(resinds)):
55 if resinds[i - 1] + 1 != resinds[i]:
57 "%s: non-sequential residue indices; mmCIF bead will cover "
59 % (str(fragment), resinds[0], resinds[-1]))
62 def _get_all_state_provenance(state_h, top_h, types):
63 """Yield all provenance information for the given state.
64 If the given State Hierarchy node contains no provenance information,
65 fall back to any provenance information for the top-level node
71 if count == 0
and top_h
is not None:
76 class _CustomDNAAlphabet(ihm.Alphabet):
77 """Custom DNA alphabet that maps A,C,G,T (rather than DA,DC,DG,DT
79 _comps = dict([cc.code_canonical, cc]
80 for cc
in ihm.DNAAlphabet._comps.values())
83 class _EntityMapper(dict):
84 """Handle mapping from IMP chains to CIF entities.
85 Multiple components may map to the same entity if they
87 def __init__(self, system):
90 self._sequence_dict = {}
92 self._alphabet_map = {
93 IMP.atom.UnknownChainType: ihm.LPeptideAlphabet,
94 IMP.atom.Protein: ihm.LPeptideAlphabet,
95 IMP.atom.RNA: ihm.RNAAlphabet,
96 IMP.atom.DNA: _CustomDNAAlphabet}
98 def _get_sequence_from_residues(self, chain, seq_from_res):
99 seq_id_begin, seq = seq_from_res
101 raise ValueError(
"Chain %s has no sequence and no residues"
103 missing_seq = [ind + seq_id_begin
104 for (ind, res)
in enumerate(seq)
if res
is None]
107 "Chain %s has no declared sequence; tried to determine the "
108 "sequence from Residues, but the following residue indices "
109 "have no residue type (perhaps covered only by Fragments): %s"
110 % (chain, str(missing_seq)))
111 return seq_id_begin - 1, tuple(seq)
113 def add(self, chain, seq_from_res=None):
114 sequence = chain.get_sequence()
115 offset = chain.get_sequence_offset()
117 if seq_from_res
is not None:
118 offset, sequence = self._get_sequence_from_residues(
121 raise ValueError(
"Chain %s has no sequence" % chain)
124 alphabet = self._alphabet_map[chain.get_chain_type()]()
125 sequence = tuple(alphabet[x]
for x
in sequence)
126 if sequence
not in self._sequence_dict:
127 entity = ihm.Entity(sequence)
128 self.system.entities.append(entity)
129 self._entities.append(entity)
130 self._sequence_dict[sequence] = entity
131 uniprot = chain.get_uniprot_accession()
133 up = ihm.reference.UniProtSequence.from_accession(uniprot)
134 entity.references.append(up)
135 self[chain] = self._sequence_dict[sequence]
136 return self[chain], offset
139 """Yield all entities"""
140 return self._entities
143 def _assign_id(obj, seen_objs, obj_by_id):
144 """Assign a unique ID to obj, and track all ids in obj_by_id."""
145 if obj
not in seen_objs:
146 if not hasattr(obj,
'id'):
147 obj_by_id.append(obj)
148 obj.id = len(obj_by_id)
149 seen_objs[obj] = obj.id
151 obj.id = seen_objs[obj]
155 """An mmCIF component. This is an instance of an _Entity. Multiple
156 _Components may map to the same _Entity but must have unique
157 asym_ids. A _Component is similar to an IMP Chain but multiple
158 Chains may map to the same _Component (the Chains represent the
159 same structure, just in different states, and potentially in
160 different IMP Models). A _Component may also represent something
161 that is described by an experiment but was not modeled by IMP, and
162 so no Chains map to it but a string name does."""
163 def __init__(self, entity, asym_id, name):
164 self.entity, self.asym_id, self.name = entity, asym_id, name
167 class _ComponentMapper:
168 """Handle mapping from IMP Chains to CIF AsymUnits."""
169 def __init__(self, system):
172 self._used_entities = set()
173 self._all_components = []
176 def __getitem__(self, chain):
177 asym_id, map_key, name = self._handle_chain(chain)
178 return self._map[map_key]
180 def _handle_chain(self, chain):
182 asym_id = chain.get_id()
183 name = mol.get_name()
if mol
else None
186 map_key =
"name", name
188 map_key =
"asym_id", asym_id
189 return asym_id, map_key, name
191 def add(self, chain, entity, offset):
192 """Add a chain (an IMP Chain object)"""
193 asym_id, map_key, name = self._handle_chain(chain)
194 if map_key
not in self._map:
195 component = _Component(entity, asym_id, name)
196 if entity
not in self._used_entities:
197 self._used_entities.add(entity)
201 entity.description = \
202 component.name.split(
"@")[0].split(
".")[0]
203 self._all_components.append(component)
204 asym = ihm.AsymUnit(entity, name, id=asym_id,
205 auth_seq_id_map=offset)
206 self.system.asym_units.append(asym)
207 component.asym_unit = asym
208 self._map[map_key] = component
210 component = self._map[map_key]
211 if component.entity != entity:
212 raise ValueError(
"Two chains have the same ID (%s) but "
213 "different sequences - rename one of the "
214 "chains" % component.asym_unit.id)
215 if component.asym_unit.auth_seq_id_map != offset:
217 "Two chains have the same ID (%s) but different offsets "
218 "(%d, %d) - this is not supported"
219 % (component.asym_unit.id,
220 component.asym_unit.auth_seq_id_map, offset))
224 """Get all components"""
225 return self._all_components
228 class _RepSegmentFactory:
229 """Make ihm.representation.Segment objects for each set of contiguous
230 particles with the same representation"""
231 def __init__(self, asym):
234 self.offset = asym.auth_seq_id_map
236 self.imp_residue_range = ()
238 def add(self, particle, starting_model):
239 """Add a new particle to the last segment (and return None).
240 Iff the particle could not be added, return the segment and start
242 (resrange, rigid_body,
243 is_res, is_atom) = self._get_particle_info(particle)
245 def start_new_segment():
246 self.particles = [particle]
247 self.imp_residue_range = resrange
248 self.rigid_body = rigid_body
250 self.is_atom = is_atom
251 self.starting_model = starting_model
252 if not self.particles:
255 elif (type(particle) == type(self.particles[0])
256 and is_res == self.is_res
257 and is_atom == self.is_atom
258 and resrange[0] <= self.imp_residue_range[1] + 1
259 and starting_model == self.starting_model
260 and self._same_rigid_body(rigid_body)):
262 self.particles.append(particle)
263 self.imp_residue_range = (self.imp_residue_range[0], resrange[1])
266 seg = self.get_last()
271 """Return the last segment, or None"""
274 asym = self.asym(self.imp_residue_range[0] - self.offset,
275 self.imp_residue_range[1] - self.offset)
277 return ihm.representation.AtomicSegment(
278 asym_unit=asym, rigid=self.rigid_body
is not None,
279 starting_model=self.starting_model)
281 return ihm.representation.ResidueSegment(
283 rigid=self.rigid_body
is not None, primitive=
'sphere',
284 starting_model=self.starting_model)
286 return ihm.representation.FeatureSegment(
288 rigid=self.rigid_body
is not None, primitive=
'sphere',
289 count=len(self.particles),
290 starting_model=self.starting_model)
292 def _same_rigid_body(self, rigid_body):
295 if self.rigid_body
is None and rigid_body
is None:
297 elif self.rigid_body
is None or rigid_body
is None:
300 return self.rigid_body == rigid_body
302 def _get_particle_info(self, p):
309 return (p.get_index(), p.get_index()), rigid_body,
True,
False
312 return (res.get_index(), res.get_index()), rigid_body,
False,
True
314 resinds = p.get_residue_indexes()
315 return (resinds[0], resinds[-1]), rigid_body,
False,
False
316 raise TypeError(
"Unknown particle ", p)
319 def _get_all_structure_provenance(p):
320 """Yield all StructureProvenance decorators for the given particle."""
324 class _StartingModelAtomHandler:
325 def __init__(self, templates, asym):
327 self._last_res_index =
None
328 self.templates = templates
331 def _residue_first_atom(self, res):
332 """Return True iff we're looking at the first atom in this residue"""
334 ind = res.get_index()
335 if ind != self._last_res_index:
336 self._last_res_index = ind
339 def handle_residue(self, res, comp_id, seq_id, offset):
340 res_name = res.get_residue_type().get_string()
344 if res_name ==
'MSE' and comp_id ==
'MET':
345 if self._residue_first_atom(res):
350 assert len(self.templates) == 0
351 self._seq_dif.append(ihm.startmodel.MSESeqDif(
352 res.get_index(), seq_id))
353 elif res_name != comp_id:
354 if self._residue_first_atom(res):
355 print(
"WARNING: Starting model residue %s does not match "
356 "that in the output model (%s) for chain %s residue %d. "
357 "Check offset (currently %d)."
358 % (res_name, comp_id, self.asym._id, seq_id, offset))
359 self._seq_dif.append(ihm.startmodel.SeqDif(
360 db_seq_id=res.get_index(), seq_id=seq_id,
362 details=
"Mutation of %s to %s" % (res_name, comp_id)))
364 def get_ihm_atoms(self, particles, offset):
368 element = atom.get_element()
370 atom_name = atom.get_atom_type().get_string()
371 het = atom_name.startswith(
'HET:')
373 atom_name = atom_name[4:]
376 seq_id = res.get_index() + offset
377 comp_id = self.asym.entity.sequence[seq_id-1].id
378 self.handle_residue(res, comp_id, seq_id, offset)
379 yield ihm.model.Atom(asym_unit=self.asym, seq_id=seq_id,
380 atom_id=atom_name, type_symbol=element,
381 x=coord[0], y=coord[1], z=coord[2],
382 het=het, biso=atom.get_temperature_factor())
385 class _StartingModel(ihm.startmodel.StartingModel):
386 _eq_keys = [
'filename',
'asym_id',
'offset']
388 def __init__(self, asym_unit, struc_prov):
389 self.filename = struc_prov[0].get_filename()
391 asym_unit=asym_unit(1, 1),
395 offset=struc_prov[0].get_residue_offset())
397 def _add_residue(self, resind):
400 seq_id_begin = self.asym_unit.seq_id_range[0]
401 if seq_id_begin == 0:
402 seq_id_begin = seq_id_end
403 self.asym_unit = self.asym_unit.asym(seq_id_begin, seq_id_end)
410 return tuple([self.__class__]
411 + [getattr(self, x)
for x
in self._eq_keys])
413 def __eq__(self, other):
414 return other
is not None and self._eq_vals() == other._eq_vals()
417 return hash(self._eq_vals())
419 def _set_sources_datasets(self, system, datasets):
421 if (hasattr(ihm.metadata,
'CIFParser')
422 and self.filename.endswith(
'.cif')):
423 p = ihm.metadata.CIFParser()
425 p = ihm.metadata.PDBParser()
426 r = p.parse_file(self.filename)
427 system.software.extend(r.get(
'software', []))
428 dataset = datasets.add(r[
'dataset'])
430 templates = r.get(
'templates', {}).get(self.asym_id, [])
433 system.locations.append(t.alignment_file)
435 datasets.add(t.dataset)
436 self.dataset = dataset
437 self.templates = templates
438 self.metadata = r.get(
'metadata', [])
440 def _read_coords(self):
441 """Read the coordinates for this starting model"""
447 rng = self.asym_unit.seq_id_range
449 hier, residue_indexes=list(range(rng[0] - self.offset,
450 rng[1] + 1 - self.offset)))
453 def get_seq_dif(self):
457 mh = _StartingModelAtomHandler(self.templates, self.asym_unit)
458 m, sel = self._read_coords()
459 for a
in mh.get_ihm_atoms(sel.get_selected_particles(), self.offset):
461 self._seq_dif = mh._seq_dif
464 class _StartingModelFinder:
465 """Map IMP particles to starting model objects"""
466 def __init__(self, asym, existing_starting_models, system, datasets):
467 self._seen_particles = {}
469 self._seen_starting_models = {}
470 for sm
in existing_starting_models:
471 self._seen_starting_models[sm] = sm
472 self._system = system
473 self._datasets = datasets
475 def find(self, particle):
476 """Return a StartingModel object, or None, for this particle"""
477 def _get_starting_model(sp, resind):
478 s = _StartingModel(self._asym, sp)
479 if s
not in self._seen_starting_models:
480 self._seen_starting_models[s] = s
481 s._set_sources_datasets(self._system, self._datasets)
482 self._system.orphan_starting_models.append(s)
483 s = self._seen_starting_models[s]
485 s._add_residue(resind)
490 sp = list(_get_all_structure_provenance(particle))
492 return _get_starting_model(sp, resind)
500 pi = h.get_particle_index()
501 seen_parents.append(pi)
503 if pi
in self._seen_particles:
504 sp = self._seen_particles[pi]
505 if sp
and sp[0]
and resind
is not None:
506 sp[0]._add_residue(resind)
507 return sp[0]
if sp
else None
509 sp = list(_get_all_structure_provenance(h))
510 self._seen_particles[pi] = []
512 s = _get_starting_model(sp, resind)
515 for spi
in seen_parents:
516 self._seen_particles[spi].append(s)
522 """Store all datasets used."""
523 def __init__(self, system):
530 """Add and return a new dataset."""
531 if d
not in self._datasets:
532 self._datasets[d] = d
533 self.system.orphan_datasets.append(d)
534 return self._datasets[d]
536 def add_group(self, datasets, name):
537 """Add and return a new group of datasets"""
541 for dataset
in datasets:
542 if dataset
not in seen:
546 if d
not in self._groups:
547 g = ihm.dataset.DatasetGroup(d, name=name)
549 self.system.orphan_dataset_groups.append(g)
550 return self._groups[d]
553 """Yield all datasets"""
554 return self._datasets.keys()
558 """Keep track of all Software objects."""
562 cites = {
'Integrative Modeling Platform (IMP)': ihm.citations.imp,
563 'IMP PMI module': ihm.citations.pmi}
565 def __init__(self, system):
567 self._by_namever = {}
572 for p
in _get_all_state_provenance(
574 self._add_provenance(p)
576 def _add_provenance(self, p):
577 """Add Software from SoftwareProvenance"""
579 name = p.get_software_name()
580 version = p.get_version()
581 if (name, version)
not in self._by_namever:
582 s = ihm.Software(name=name,
583 classification=
'integrative model building',
584 description=
None, version=version,
585 location=p.get_location(),
586 citation=self.cites.get(name))
587 self.system.software.append(s)
588 self._by_namever[name, version] = s
589 return self._by_namever[name, version]
591 def _add_previous_provenance(self, prov):
592 """Add Software from a previous SoftwareProvenance, if any"""
596 prov = prov.get_previous()
599 class _ExternalFiles:
600 """Track all externally-referenced files
601 (i.e. anything that refers to a Location that isn't
602 a DatabaseLocation)."""
603 def __init__(self, system):
609 for p
in _get_all_state_provenance(
611 self._add_provenance(p)
613 def _add_provenance(self, p):
614 """Add external file from ScriptProvenance"""
616 path = p.get_filename()
617 if path
not in self._by_path:
618 loc = ihm.location.WorkflowFileLocation(
619 path=p.get_filename(),
620 details=
'Integrative modeling Python script')
621 self.system.locations.append(loc)
622 self._by_path[path] = loc
623 return self._by_path[path]
626 class _ProtocolStep(ihm.protocol.Step):
627 """A single step (e.g. sampling, refinement) in a protocol."""
628 def __init__(self, prov, num_models_begin, assembly, all_software):
629 method = prov.get_method()
630 if prov.get_number_of_replicas() > 1:
631 method =
"Replica exchange " + method
636 method=method, name=
'Sampling',
637 num_models_begin=num_models_begin,
638 num_models_end=prov.get_number_of_frames(),
640 multi_state=
False, ordered=
False,
643 software=all_software._add_previous_provenance(prov))
645 def add_combine(self, prov):
646 self.num_models_end = prov.get_number_of_frames()
647 return self.num_models_end
650 class _Protocol(ihm.protocol.Protocol):
651 """A modeling protocol.
652 Each protocol consists of a number of protocol steps (e.g. sampling,
653 refinement) followed by a number of postprocessing steps (e.g.
654 filtering, rescoring, clustering)"""
656 def add_step(self, prov, num_models, assembly, all_software):
659 if len(self.steps) == 0:
660 raise ValueError(
"CombineProvenance with no previous sampling")
661 return self.steps[-1].add_combine(prov)
663 ps = _ProtocolStep(prov, num_models, assembly, all_software)
664 self.steps.append(ps)
665 return ps.num_models_end
667 def add_postproc(self, prov, num_models, assembly):
668 if not self.analyses:
669 self.analyses.append(ihm.analysis.Analysis())
671 pp = ihm.analysis.FilterStep(
672 feature=
'energy/score', assembly=assembly,
673 num_models_begin=num_models,
674 num_models_end=prov.get_number_of_frames())
677 pp = ihm.analysis.ClusterStep(
678 feature=
'RMSD', assembly=assembly, num_models_begin=num_models,
679 num_models_end=num_models)
681 raise ValueError(
"Unhandled provenance", prov)
682 self.analyses[-1].steps.append(pp)
683 return pp.num_models_end
687 """Track all modeling protocols used."""
688 def __init__(self, system):
691 def _add_protocol(self, prot):
697 def step_equal(x, y):
699 return {x: y
for x, y
in d.__dict__.items()
700 if x !=
'dataset_group'}
702 return (type(x) == type(y)
703 and get_dict(x) == get_dict(y))
705 def analysis_equal(x, y):
706 return (len(x.steps) == len(y.steps)
707 and all(step_equal(a, b)
708 for (a, b)
in zip(x.steps, y.steps)))
710 for existing
in self.system.orphan_protocols:
711 if (len(existing.steps) == len(prot.steps)
712 and len(existing.analyses) == len(prot.analyses)
713 and all(step_equal(x, y)
714 for (x, y)
in zip(existing.steps, prot.steps))
715 and all(analysis_equal(x, y)
716 for (x, y)
in zip(existing.analyses, prot.analyses))):
718 self.system.orphan_protocols.append(prot)
721 def _add_hierarchy(self, h, top_h, modeled_assembly, all_software):
727 for p
in reversed(list(_get_all_state_provenance(
728 h, top_h, types=prot_types + pp_types))):
729 if isinstance(p, pp_types):
730 num_models = prot.add_postproc(p, num_models, modeled_assembly)
735 self._add_protocol(prot)
737 num_models = prot.add_step(p, num_models, modeled_assembly,
740 if len(prot.steps) > 0:
741 return self._add_protocol(prot)
744 class _CoordinateHandler:
745 def __init__(self, system, datasets):
746 self._system = system
747 self._datasets = datasets
748 self._representation = ihm.representation.Representation()
756 def get_residue_sequence(self, ps):
757 """Determine the primary sequence based on Residue particles.
758 Return the index of the first residue and the sequence, as a list
759 of ihm.ChemComp objects (or None)"""
764 restyp[residue.get_index()] = residue.get_residue_type()
766 restyp[p.get_index()] = p.get_residue_type()
768 resinds = p.get_residue_indexes()
774 seq_id_begin = min(restyp.keys())
775 seq_id_end = max(restyp.keys())
776 return (seq_id_begin,
777 [_imp_to_ihm[restyp.get(x)]
778 for x
in range(seq_id_begin, seq_id_end + 1)])
780 def add_chain(self, ps, asym):
783 return s == asym
or hasattr(s,
'asym')
and s.asym == asym
787 smf = _StartingModelFinder(
788 asym, [s
for s
in self._system.orphan_starting_models
789 if matches_asym(s.asym_unit)],
790 self._system, self._datasets)
791 segfactory = _RepSegmentFactory(asym)
792 offset = asym.auth_seq_id_map
794 starting_model = smf.find(p)
795 seg = segfactory.add(p, starting_model)
797 self._representation.append(seg)
798 self._add_atom_or_sphere(p, asym, offset)
799 last = segfactory.get_last()
801 self._representation.append(last)
803 def _add_atom_or_sphere(self, p, asym, offset):
807 element = p.get_element()
809 atom_name = p.get_atom_type().get_string()
810 het = atom_name.startswith(
'HET:')
812 atom_name = atom_name[4:]
813 self._atoms.append(ihm.model.Atom(
814 asym_unit=asym, seq_id=residue.get_index() - offset,
815 atom_id=atom_name, type_symbol=element,
816 x=xyz[0], y=xyz[1], z=xyz[2], het=het,
817 biso=p.get_temperature_factor(),
818 occupancy=p.get_occupancy()))
821 resinds = p.get_residue_indexes()
825 sbegin = send = p.get_index()
827 xyz = xyzr.get_coordinates()
828 self._spheres.append(ihm.model.Sphere(
829 asym_unit=asym, seq_id_range=(sbegin - offset, send - offset),
830 x=xyz[0], y=xyz[1], z=xyz[2], radius=xyzr.get_radius()))
832 def get_structure_particles(self, h):
833 """Return particles sorted by residue index"""
835 if h.get_number_of_children() == 0:
837 if not h.get_is_valid():
838 raise ValueError(
"Invalid hierarchy as input")
840 hierarchy=h, resolution=0.).get_selected_particles():
843 ps.append((residue.get_index(), residue))
846 resinds = fragment.get_residue_indexes()
847 _check_sequential(fragment, resinds)
848 resind = resinds[len(resinds) // 2]
849 ps.append((resind, fragment))
853 ps.append((residue.get_index(), atom))
854 return [p[1]
for p
in sorted(ps, key=operator.itemgetter(0))]
857 class _ModelAssemblies:
858 def __init__(self, system):
860 self._seen_assemblies = {}
862 def add(self, asyms):
865 if asyms
not in self._seen_assemblies:
866 assembly = ihm.Assembly(
867 asyms, name=
"Modeled assembly",
868 description=
"All components modeled by IMP")
869 self.system.orphan_assemblies.append(assembly)
870 self._seen_assemblies[asyms] = assembly
871 return self._seen_assemblies[asyms]
874 class _Representations:
875 def __init__(self, system):
882 for existing
in self.system.orphan_representations:
883 if (len(existing) == len(rep)
884 and all(type(x) == type(y)
885 and x.__dict__ == y.__dict__
886 for (x, y)
in zip(existing, rep))):
888 self.system.orphan_representations.append(rep)
Select non water and non hydrogen atoms.
static bool get_is_setup(const IMP::ParticleAdaptor &p)
Hierarchy read_pdb_or_mmcif(TextInput input, Model *model, PDBSelector *selector=get_default_pdb_selector(), bool select_first_model=true)
Read all the molecules in the first model of the PDB or mmCIF file.
static bool get_is_setup(const IMP::ParticleAdaptor &p)
A decorator to associate a particle with a part of a protein/DNA/RNA.
Track creation of a system fragment from running some software.
static bool get_is_setup(const IMP::ParticleAdaptor &p)
ElementTable & get_element_table()
Track creation of a system fragment from sampling.
def get_molecule
Given a Hierarchy, walk up and find the parent Molecule.
static bool get_is_setup(const IMP::ParticleAdaptor &p)
Track creation of a system fragment by combination.
Class for storing model, its restraints, constraints, and particles.
static bool get_is_setup(Model *m, ParticleIndex pi)
void add_hierarchy(RMF::FileHandle fh, atom::Hierarchy hs)
The standard decorator for manipulating molecular structures.
Ints get_index(const ParticlesTemp &particles, const Subset &subset, const Subsets &excluded)
A decorator for a particle representing an atom.
Track creation of a system fragment by filtering.
Track creation of a system fragment from a PDB file.
A decorator for a particle with x,y,z coordinates.
A decorator for a residue.
static bool get_is_setup(Model *m, ParticleIndex p)
Check if the particle has the needed attributes for a cast to succeed.
Residue get_residue(Atom d, bool nothrow=false)
Return the Residue containing this atom.
Track creation of a system fragment from clustering.
static bool get_is_setup(Model *m, ParticleIndex pi)
Functionality for loading, creating, manipulating and scoring atomic structures.
std::string get_chain_id(Hierarchy h)
Walk up the hierarchy to determine the chain id.
def get_all_provenance
Yield all provenance decorators of the given types for the particle.
A decorator for a molecule.
Select hierarchy particles identified by the biological name.
Select all ATOM and HETATM records with the given chain ids.
Track creation of a system fragment from running a script.
A decorator for a particle with x,y,z coordinates and a radius.