From 4436041d78e1fc58b220336342418db94b2bae23 Mon Sep 17 00:00:00 2001 From: Ben Webb Date: Fri, 22 Jun 2018 16:13:36 -0700 Subject: [PATCH] Add basic support for ProtocolOutput to PMI2 Allow attaching ProtocolOutput objects to PMI2 Systems. This will allow us to collect information about a PMI2 modeling run, for example to deposit the protocol as an mmCIF file. Relates #244. --- pyext/src/mmcif.py | 7 +++-- pyext/src/topology/__init__.py | 32 +++++++++++++++++++++- test/test_mmcif_pmi2.py | 50 ++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 test/test_mmcif_pmi2.py diff --git a/pyext/src/mmcif.py b/pyext/src/mmcif.py index b5ab55da..2ae61301 100644 --- a/pyext/src/mmcif.py +++ b/pyext/src/mmcif.py @@ -941,9 +941,12 @@ def __init__(self, pmi_object, po): # Point to the PMI object for this state. Use a weak reference # since the state object typically points to us too, so we need # to break the reference cycle. In PMI1 this will be a - # Representation object. + # Representation object; in PMI2 it is the PMI2 State object itself. self._pmi_object = weakref.proxy(pmi_object) - self._pmi_state = pmi_object.state + if hasattr(pmi_object, 'state'): + self._pmi_state = pmi_object.state + else: + self._pmi_state = self._pmi_object # Preserve PMI state name old_name = self.name super(_State, self).__init__(experiment_type='Fraction of bulk') diff --git a/pyext/src/topology/__init__.py b/pyext/src/topology/__init__.py index 8665cb23..b6fbdd9f 100644 --- a/pyext/src/topology/__init__.py +++ b/pyext/src/topology/__init__.py @@ -106,6 +106,7 @@ class System(_SystemBase): def __init__(self,mdl=None,name="System"): _SystemBase.__init__(self,mdl) self._number_of_states = 0 + self._protocol_output = [] self.states = [] self.built=False @@ -141,6 +142,17 @@ def build(self,**kwargs): self.built=True return self.hier + def add_protocol_output(self, p): + """Capture details of the modeling protocol. + @param p an instance of IMP.pmi.output.ProtocolOutput or a subclass. + """ + self._protocol_output.append(p) +# p._each_metadata.append(self._metadata) +# p._file_datasets.append(self._file_dataset) + for state in self.states: + state._add_protocol_output(p, self) + + #------------------------ class State(_SystemBase): @@ -157,14 +169,24 @@ def __init__(self,system,state_index): self.mdl = system.get_hierarchy().get_model() self.system = system self.hier = self._create_child(system.get_hierarchy()) - self.hier.set_name("State_"+str(state_index)) + self.short_name = self.long_name = "State_"+str(state_index) + self.hier.set_name(self.short_name) self.molecules = defaultdict(list) # key is molecule name. value are the molecule copies! IMP.atom.State.setup_particle(self.hier,state_index) self.built = False + self._protocol_output = [] + for p in system._protocol_output: + self._add_protocol_output(p, system) def __repr__(self): return self.system.__repr__()+'.'+self.hier.get_name() + def _add_protocol_output(self, p, system): + state = p._add_state(self) + self._protocol_output.append((p, state)) + state.m = system.mdl + state.prot = self.hier + def get_molecules(self): """Return a dictionary where key is molecule name and value are the list of all copies of that molecule in setup order""" @@ -553,6 +575,9 @@ def add_representation(self, ideal_helix, color)) + def _all_protocol_output(self): + return self.state._protocol_output + def build(self): """Create all parts of the IMP hierarchy including Atoms, Residues, and Fragments/Representations and, finally, Copies @@ -562,6 +587,11 @@ def build(self): from the PDB file """ if not self.built: + # Add molecule name and sequence to any ProtocolOutput objects + name = self.hier.get_name() + for po, state in self._all_protocol_output(): + po.create_component(state, name, True) + po.add_component_sequence(state, name, self.sequence) # if requested, clone structure and representations BEFORE building original if self.mol_to_clone is not None: for nr,r in enumerate(self.mol_to_clone.residues): diff --git a/test/test_mmcif_pmi2.py b/test/test_mmcif_pmi2.py new file mode 100644 index 00000000..df61b32b --- /dev/null +++ b/test/test_mmcif_pmi2.py @@ -0,0 +1,50 @@ +from __future__ import print_function + +import ihm.format +import IMP.test +import IMP.pmi.topology +import IMP.pmi.mmcif +import sys + +if sys.version_info[0] >= 3: + from io import StringIO +else: + from io import BytesIO as StringIO + +class DummyPO(IMP.pmi.mmcif.ProtocolOutput): + def flush(self): + pass + +class Tests(IMP.test.TestCase): + + def test_entity(self): + """Test EntityDump with PMI2-style init""" + m = IMP.Model() + s = IMP.pmi.topology.System(m) + po = DummyPO(None) + s.add_protocol_output(po) + state = s.create_state() + nup84 = state.create_molecule("Nup84", "MELS", "A") + nup84.add_representation(resolutions=[1]) + hier = s.build() + fh = StringIO() + w = ihm.format.CifWriter(fh) + d = ihm.dumper._EntityDumper() + d.finalize(po.system) + d.dump(po.system, w) + out = fh.getvalue() + self.assertEqual(out, """# +loop_ +_entity.id +_entity.type +_entity.src_method +_entity.pdbx_description +_entity.formula_weight +_entity.pdbx_number_of_molecules +_entity.details +1 polymer man Nup84 ? 1 . +# +""") + +if __name__ == '__main__': + IMP.test.main()