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

Source Code for Package data

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