Package data_store
[hide private]
[frames] | no frames]

Source Code for Package data_store

  1  ############################################################################### 
  2  #                                                                             # 
  3  # Copyright (C) 2003-2014 Edward d'Auvergne                                   # 
  4  #                                                                             # 
  5  # This file is part of the program relax (http://www.nmr-relax.com).          # 
  6  #                                                                             # 
  7  # This program is free software: you can redistribute it and/or modify        # 
  8  # it under the terms of the GNU General Public License as published by        # 
  9  # the Free Software Foundation, either version 3 of the License, or           # 
 10  # (at your option) any later version.                                         # 
 11  #                                                                             # 
 12  # This program is distributed in the hope that it will be useful,             # 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of              # 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               # 
 15  # GNU General Public License for more details.                                # 
 16  #                                                                             # 
 17  # You should have received a copy of the GNU General Public License           # 
 18  # along with this program.  If not, see <http://www.gnu.org/licenses/>.       # 
 19  #                                                                             # 
 20  ############################################################################### 
 21   
 22  # Module docstring. 
 23  """Package containing the relax data storage object.""" 
 24   
 25   
 26  # Python module imports. 
 27  from re import search 
 28  from sys import stderr 
 29  from time import asctime 
 30  import xml.dom.minidom 
 31   
 32  # relax module imports. 
 33  from compat import builtins 
 34  from data_store.gui import Gui 
 35  from data_store.pipe_container import PipeContainer 
 36  from data_store.relax_xml import fill_object_contents, xml_to_object 
 37  import pipe_control 
 38  from lib.errors import RelaxError, RelaxPipeError, RelaxNoPipeError 
 39  from status import Status; status = Status() 
 40  import version 
 41   
 42   
 43  __all__ = [ 'align_tensor', 
 44              'data_classes', 
 45              'diff_tensor', 
 46              'exp_info', 
 47              'gui', 
 48              'interatomic', 
 49              'mol_res_spin', 
 50              'pipe_container', 
 51              'prototype', 
 52              'relax_xml' 
 53  ] 
 54   
 55   
56 -class Relax_data_store(dict):
57 """The relax data storage object.""" 58 59 # The current data pipe. 60 current_pipe = None 61 builtins.cdp = None 62 63 # Class variable for storing the class instance. 64 instance = None 65
66 - def __new__(self, *args, **kargs):
67 """Replacement function for implementing the singleton design pattern.""" 68 69 # First initialisation. 70 if self.instance is None: 71 # Create a new instance. 72 self.instance = dict.__new__(self, *args, **kargs) 73 74 # Add some initial structures. 75 self.instance.pipe_bundles = {} 76 self.instance.relax_gui = Gui() 77 78 # Already initialised, so return the instance. 79 return self.instance
80 81
82 - def __repr__(self):
83 """The string representation of the object. 84 85 Rather than using the standard Python conventions (either the string representation of the 86 value or the "<...desc...>" notation), a rich-formatted description of the object is given. 87 """ 88 89 # Intro text. 90 text = "The relax data storage object.\n" 91 92 # The data pipes. 93 text = text + "\n" 94 text = text + "Data pipes:\n" 95 pipes = list(self.instance.keys()) 96 if pipes: 97 for pipe in pipes: 98 text = text + " %s\n" % repr(pipe) 99 else: 100 text = text + " None\n" 101 102 # Data store objects. 103 text = text + "\n" 104 text = text + "Data store objects:\n" 105 names = sorted(self.__class__.__dict__.keys()) 106 for name in names: 107 # The object. 108 obj = getattr(self, name) 109 110 # The text. 111 if obj == None or isinstance(obj, str): 112 text = text + " %s %s: %s\n" % (name, type(obj), obj) 113 else: 114 text = text + " %s %s: %s\n" % (name, type(obj), obj.__doc__.split('\n')[0]) 115 116 # dict methods. 117 text = text + "\n" 118 text = text + "Inherited dictionary methods:\n" 119 for name in dir(dict): 120 # Skip special methods. 121 if search("^_", name): 122 continue 123 124 # Skip overwritten methods. 125 if name in list(self.__class__.__dict__.keys()): 126 continue 127 128 # The object. 129 obj = getattr(self, name) 130 131 # The text. 132 text = text + " %s %s: %s\n" % (name, type(obj), obj.__doc__.split('\n')[0]) 133 134 # All other objects. 135 text = text + "\n" 136 text = text + "All other objects:\n" 137 for name in dir(self): 138 # Skip special methods. 139 if search("^_", name): 140 continue 141 142 # Skip overwritten methods. 143 if name in list(self.__class__.__dict__.keys()): 144 continue 145 146 # Skip dictionary methods. 147 if name in dir(dict): 148 continue 149 150 # The object. 151 obj = getattr(self, name) 152 153 # The text. 154 text = text + " %s %s: %s\n" % (name, type(obj), obj) 155 156 # Return the text. 157 return text
158 159
160 - def __reset__(self):
161 """Delete all the data from the relax data storage object. 162 163 This method is to make the current single instance of the Data object identical to a newly 164 created instance of Data, hence resetting the relax program state. 165 """ 166 167 # Loop over the keys of self.__dict__ and delete the corresponding object. 168 for key in list(self.__dict__.keys()): 169 # Delete the object. 170 del self.__dict__[key] 171 172 # Remove all items from the dictionary. 173 self.instance.clear() 174 175 # Reset the current data pipe. 176 builtins.cdp = None 177 178 # Recreate the pipe bundle object. 179 self.instance.pipe_bundles = {} 180 181 # Re-add the GUI object. 182 self.instance.relax_gui = Gui() 183 184 # Signal the change. 185 status.observers.reset.notify() 186 status.observers.pipe_alteration.notify()
187 188
189 - def _back_compat_hook(self, file_version=None, pipes=None):
190 """Method for converting the old data structures to the new ones. 191 192 @keyword file_version: The relax XML version of the XML file. 193 @type file_version: int 194 @keyword pipes: The list of new pipe names to update. 195 @type pipes: list of str 196 """ 197 198 # Loop over the new data pipes. 199 for pipe_name in pipes: 200 # The data pipe object. 201 dp = self[pipe_name] 202 203 # Convert the molecule-residue-spin data. 204 for mol in dp.mol: 205 # Loop over the residues. 206 for res in mol.res: 207 # Loop over the spins. 208 for spin in res.spin: 209 # The current spin ID. 210 spin_id = pipe_control.mol_res_spin.generate_spin_id_unique(pipe_cont=dp, mol=mol, res=res, spin=spin) 211 212 # Convert proton spins (the 'heteronuc_type' variable indicates a pre-interatomic container design state). 213 if hasattr(spin, 'heteronuc_type') and hasattr(spin, 'element') and spin.element == 'H': 214 # Rename the nuclear isotope. 215 spin.isotope = spin.proton_type 216 217 # Convert heteronuclear spins (the 'heteronuc_type' variable indicates a pre-interatomic container design state). 218 elif hasattr(spin, 'heteronuc_type'): 219 # Rename the nuclear isotope. 220 spin.isotope = spin.heteronuc_type 221 222 # Name the spin if needed. 223 if spin.name == None: 224 if search('N', spin.isotope): 225 pipe_control.mol_res_spin.name_spin(spin_id=spin_id, name='N', pipe=pipe_name) 226 elif search('C', spin.isotope): 227 pipe_control.mol_res_spin.name_spin(spin_id=spin_id, name='C', pipe=pipe_name) 228 229 # An attached proton - convert into a spin container. 230 if (hasattr(spin, 'attached_proton') and spin.attached_proton != None) or (hasattr(spin, 'proton_type') and spin.proton_type != None): 231 # The proton name. 232 if hasattr(spin, 'attached_proton') and spin.attached_proton != None: 233 proton_name = spin.attached_proton 234 else: 235 proton_name = 'H' 236 237 # The two spin IDs (newly regenerated due to the above renaming). 238 spin_id1 = pipe_control.mol_res_spin.generate_spin_id_unique(pipe_cont=dp, mol=mol, res=res, spin=spin) 239 spin_id2 = pipe_control.mol_res_spin.generate_spin_id_unique(pipe_cont=dp, mol=mol, res=res, spin_name=proton_name) 240 241 # Fetch the proton spin if it exists. 242 h_spin = pipe_control.mol_res_spin.return_spin(spin_id2, pipe=pipe_name) 243 if h_spin: 244 spin_id2 = pipe_control.mol_res_spin.generate_spin_id_unique(pipe_cont=dp, mol=mol, res=res, spin_name=proton_name, spin_num=h_spin.num) 245 246 # Create a new spin container for the proton if needed. 247 if not h_spin: 248 h_spin = pipe_control.mol_res_spin.create_spin(mol_name=mol.name, res_num=res.num, res_name=res.name, spin_name=proton_name, pipe=pipe_name) 249 h_spin.select = False 250 251 # Set up a dipole interaction between the two spins if needed. 252 if not hasattr(h_spin, 'element'): 253 pipe_control.mol_res_spin.set_spin_element(spin_id=spin_id2, element='H', pipe=pipe_name) 254 if not hasattr(h_spin, 'isotope'): 255 pipe_control.mol_res_spin.set_spin_isotope(spin_id=spin_id2, isotope='1H', pipe=pipe_name) 256 pipe_control.interatomic.define(spin_id1, spin_id2, verbose=False, pipe=pipe_name) 257 258 # Get the interatomic data container. 259 interatom = pipe_control.interatomic.return_interatom(spin_id1=spin_id1, spin_id2=spin_id2, pipe=pipe_name) 260 261 # Set the interatomic distance. 262 if hasattr(spin, 'r'): 263 interatom.r = spin.r 264 265 # Set the interatomic unit vectors. 266 if hasattr(spin, 'xh_vect'): 267 interatom.vector = spin.xh_vect 268 269 # Set the RDC values. 270 if hasattr(spin, 'rdc'): 271 interatom.rdc = spin.rdc 272 if hasattr(spin, 'rdc_err'): 273 interatom.rdc_err = spin.rdc_err 274 if hasattr(spin, 'rdc_sim'): 275 interatom.rdc_sim = spin.rdc_sim 276 if hasattr(spin, 'rdc_bc'): 277 interatom.rdc_bc = spin.rdc_bc 278 279 # Delete the old structures. 280 eliminate = ['heteronuc_type', 'proton_type', 'attached_proton', 'r', 'r_err', 'r_sim', 'rdc', 'rdc_err', 'rdc_sim', 'rdc_bc', 'xh_vect'] 281 for name in eliminate: 282 if hasattr(spin, name): 283 delattr(spin, name) 284 285 # Conversions for the interatomic data containers. 286 if hasattr(dp, 'interatomic'): 287 for interatom in dp.interatomic: 288 # RDC data. 289 if hasattr(interatom, 'rdc') and not hasattr(interatom, 'rdc_data_types'): 290 # Initialise. 291 interatom.rdc_data_types = {} 292 293 # Add the data type, assumed to be 'D', for each alignment ID. 294 for id in dp.rdc_ids: 295 interatom.rdc_data_types[id] = 'D' 296 297 # Convert the alignment tensors. 298 if hasattr(dp, 'align_tensors'): 299 for i in range(len(dp.align_tensors)): 300 # Fix for the addition of the alignment ID structure as opposed to the tensor name or tag. 301 if not hasattr(dp.align_tensors[i], 'align_id'): 302 dp.align_tensors[i].set('align_id', dp.align_tensors[i].name) 303 304 # Convert spectrometer frequency information. 305 if hasattr(dp, 'frq'): 306 # Convert to the new structure. 307 dp.spectrometer_frq = dp.frq 308 del dp.frq 309 310 # Build the new frequency list structure. 311 dp.spectrometer_frq_list = [] 312 for frq in dp.spectrometer_frq.values(): 313 if frq not in dp.spectrometer_frq_list: 314 dp.spectrometer_frq_list.append(frq) 315 316 # And finally count the elements. 317 dp.spectrometer_frq_count = len(dp.spectrometer_frq_list)
318 319
320 - def add(self, pipe_name, pipe_type, bundle=None, switch=True):
321 """Method for adding a new data pipe container to the dictionary. 322 323 This method should be used rather than importing the PipeContainer class and using the statement 'D[pipe] = PipeContainer()', where D is the relax data storage object and pipe is the name of the data pipe. 324 325 @param pipe_name: The name of the new data pipe. 326 @type pipe_name: str 327 @param pipe_type: The data pipe type. 328 @type pipe_type: str 329 @keyword bundle: The optional data pipe bundle to associate the data pipe with. 330 @type bundle: str or None 331 @keyword switch: A flag which if True will cause the new data pipe to be set to the current data pipe. 332 @type switch: bool 333 """ 334 335 # Test if the pipe already exists. 336 if pipe_name in list(self.instance.keys()): 337 raise RelaxPipeError(pipe_name) 338 339 # Create a new container. 340 self[pipe_name] = PipeContainer() 341 342 # Add the data pipe type string to the container. 343 self[pipe_name].pipe_type = pipe_type 344 345 # The pipe bundle. 346 if bundle: 347 # A new bundle. 348 if bundle not in list(self.pipe_bundles.keys()): 349 self.pipe_bundles[bundle] = [] 350 351 # Add the pipe to the bundle. 352 self.pipe_bundles[bundle].append(pipe_name) 353 354 # Change the current data pipe. 355 if switch: 356 # Set the current data pipe. 357 self.instance.current_pipe = pipe_name 358 builtins.cdp = self[pipe_name] 359 360 # Signal the switch. 361 status.observers.pipe_alteration.notify()
362 363
364 - def is_empty(self, verbosity=False):
365 """Method for testing if the relax data store is empty. 366 367 @keyword verbosity: A flag which if True will cause messages to be printed to STDERR. 368 @type verbosity: bool 369 @return: True if the data store is empty, False otherwise. 370 @rtype: bool 371 """ 372 373 # No pipes should exist. 374 if not list(self.keys()) == []: 375 if verbosity: 376 stderr.write("The relax data store contains the data pipes %s.\n" % list(self.keys())) 377 return False 378 379 # Objects which should be in here. 380 blacklist = [ 381 'pipe_bundles', 382 'relax_gui' 383 ] 384 385 # An object has been added to the data store. 386 for name in dir(self): 387 # Skip the data store methods. 388 if name in list(self.__class__.__dict__.keys()): 389 continue 390 391 # Skip the dict methods. 392 if name in list(dict.__dict__.keys()): 393 continue 394 395 # Skip special objects. 396 if search("^__", name): 397 continue 398 399 # Blacklisted objects to skip. 400 if name in blacklist: 401 continue 402 403 # An object has been added. 404 if verbosity: 405 stderr.write("The relax data store contains the object %s.\n" % name) 406 return False 407 408 # The data store is empty. 409 return True
410 411
412 - def from_xml(self, file, dir=None, pipe_to=None, verbosity=1):
413 """Parse a XML document representation of a data pipe, and load it into the relax data store. 414 415 @param file: The open file object. 416 @type file: file 417 @keyword dir: The name of the directory containing the results file (needed 418 for loading external files). 419 @type dir: str 420 @keyword pipe_to: The data pipe to load the XML data pipe into (the file must only 421 contain one data pipe). 422 @type pipe_to: str 423 @keyword verbosity: A flag specifying the amount of information to print. The 424 higher the value, the greater the verbosity. 425 @type verbosity: int 426 @raises RelaxError: If pipe_to is given and the file contains multiple pipe 427 elements; or if the data pipes in the XML file already exist in 428 the relax data store; or if the data pipe type is invalid; or 429 if the target data pipe is not empty. 430 @raises RelaxNoPipeError: If pipe_to is given but the data pipe does not exist. 431 @raises RelaxError: If the data pipes in the XML file already exist in the relax 432 data store, or if the data pipe type is invalid. 433 @raises RelaxPipeError: If the data pipes of the XML file are already present in the 434 relax data store. 435 """ 436 437 # Create the XML document from the file. 438 doc = xml.dom.minidom.parse(file) 439 440 # Get the relax node. 441 relax_node = doc.childNodes[0] 442 443 # Get the relax version of the XML file. 444 file_version = relax_node.getAttribute('file_version') 445 if file_version == '': 446 file_version = 1 447 else: 448 file_version = int(file_version) 449 450 # Get the GUI nodes. 451 gui_nodes = relax_node.getElementsByTagName('relax_gui') 452 if gui_nodes: 453 self.relax_gui.from_xml(gui_nodes[0], file_version=file_version) 454 455 # Recreate all the data store data structures. 456 xml_to_object(relax_node, self, file_version=file_version, blacklist=['pipe', 'relax_gui']) 457 458 # Get the pipe nodes. 459 pipe_nodes = relax_node.getElementsByTagName('pipe') 460 461 # Structure for the names of the new pipes. 462 pipes = [] 463 464 # Target loading to a specific pipe (for pipe results reading). 465 if pipe_to: 466 # Check if there are multiple pipes in the XML file. 467 if len(pipe_nodes) > 1: 468 raise RelaxError("The pipe_to target pipe argument '%s' cannot be given as the file contains multiple pipe elements." % pipe_to) 469 470 # The pipe type. 471 pipe_type = pipe_nodes[0].getAttribute('type') 472 473 # Check that the pipe already exists. 474 if not pipe_to in self: 475 raise RelaxNoPipeError(pipe_to) 476 477 # Check if the pipe type matches. 478 if pipe_type != self[pipe_to].pipe_type: 479 raise RelaxError("The XML file pipe type '%s' does not match the pipe type '%s'" % (pipe_type, self[pipe_to].pipe_type)) 480 481 # Check if the pipe is empty. 482 if not self[pipe_to].is_empty(): 483 raise RelaxError("The data pipe '%s' is not empty." % pipe_to) 484 485 # Load the data. 486 self[pipe_to].from_xml(pipe_nodes[0], dir=dir, file_version=file_version) 487 488 # Store the pipe name. 489 pipes.append(pipe_to) 490 491 # Load the state. 492 else: 493 # Checks. 494 for pipe_node in pipe_nodes: 495 # The pipe name and type. 496 pipe_name = str(pipe_node.getAttribute('name')) 497 pipe_type = pipe_node.getAttribute('type') 498 499 # Existence check. 500 if pipe_name in self: 501 raise RelaxPipeError(pipe_name) 502 503 # Valid type check. 504 if not pipe_type in pipe_control.pipes.VALID_TYPES: 505 raise RelaxError("The data pipe type '%s' is invalid and must be one of the strings in the list %s." % (pipe_type, pipe_control.pipes.VALID_TYPES)) 506 507 # Load the data pipes. 508 for pipe_node in pipe_nodes: 509 # The pipe name and type. 510 pipe_name = str(pipe_node.getAttribute('name')) 511 pipe_type = pipe_node.getAttribute('type') 512 513 # Add the data pipe. 514 switch = False 515 if self.current_pipe == None: 516 switch = True 517 self.add(pipe_name, pipe_type, switch=switch) 518 519 # Fill the pipe. 520 self[pipe_name].from_xml(pipe_node, file_version=file_version, dir=dir) 521 522 # Store the pipe name. 523 pipes.append(pipe_name) 524 525 # Set the current pipe. 526 if self.current_pipe in list(self.keys()): 527 builtins.cdp = self[self.current_pipe] 528 529 # Finally update the molecule, residue, and spin metadata for each data pipe. 530 for pipe in pipes: 531 pipe_control.mol_res_spin.metadata_update(pipe=pipe) 532 533 # Backwards compatibility transformations. 534 self._back_compat_hook(file_version, pipes=pipes)
535 536
537 - def to_xml(self, file, pipes=None):
538 """Create a XML document representation of the current data pipe. 539 540 This method creates the top level XML document including all the information needed 541 about relax, calls the PipeContainer.xml_write() method to fill in the document contents, 542 and writes the XML into the file object. 543 544 @param file: The open file object. 545 @type file: file 546 @param pipes: The name of the pipe, or list of pipes to place in the XML file. 547 @type pipes: str or list of str 548 """ 549 550 # The pipes to include in the XML file. 551 all = False 552 if not pipes: 553 all = True 554 pipes = list(self.keys()) 555 elif isinstance(pipes, str): 556 pipes = [pipes] 557 558 # Sort the pipes. 559 pipes.sort() 560 561 # Create the XML document object. 562 xmldoc = xml.dom.minidom.Document() 563 564 # Create the top level element, including the relax URL. 565 top_element = xmldoc.createElementNS('http://www.nmr-relax.com', 'relax') 566 top_element.setAttribute("xmlns", "http://www.nmr-relax.com") 567 568 # Append the element. 569 xmldoc.appendChild(top_element) 570 571 # Set the relax version number, and add a creation time. 572 top_element.setAttribute('version', version.version) 573 top_element.setAttribute('time', asctime()) 574 top_element.setAttribute('file_version', "2") 575 rev = version.revision() 576 if rev: 577 top_element.setAttribute('revision', rev) 578 url = version.url() 579 if url: 580 top_element.setAttribute('url', url) 581 582 # Add all objects in the data store base object to the XML element. 583 if all: 584 blacklist = list(list(self.__class__.__dict__.keys()) + list(dict.__dict__.keys())) 585 for name in dir(self): 586 # Skip blacklisted objects. 587 if name in blacklist: 588 continue 589 590 # Skip special objects. 591 if search('^_', name): 592 continue 593 594 # Execute any to_xml() methods, and add that object to the blacklist. 595 obj = getattr(self, name) 596 if hasattr(obj, 'to_xml'): 597 obj.to_xml(xmldoc, top_element) 598 blacklist = blacklist + [name] 599 600 # Remove the current data pipe from the blacklist! 601 blacklist.remove('current_pipe') 602 603 # Add all simple python objects within the store. 604 fill_object_contents(xmldoc, top_element, object=self, blacklist=blacklist) 605 606 # Loop over the pipes. 607 for pipe in pipes: 608 # Create the pipe XML element and add it to the top level XML element. 609 pipe_element = xmldoc.createElement('pipe') 610 top_element.appendChild(pipe_element) 611 612 # Set the data pipe attributes. 613 pipe_element.setAttribute('desc', 'The contents of a relax data pipe') 614 pipe_element.setAttribute('name', pipe) 615 pipe_element.setAttribute('type', self[pipe].pipe_type) 616 617 # Fill the data pipe XML element. 618 self[pipe].to_xml(xmldoc, pipe_element, pipe_type=self[pipe].pipe_type) 619 620 # Write out the XML file. 621 file.write(xmldoc.toprettyxml(indent=' '))
622