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

Source Code for Package data

  1  ############################################################################### 
  2  #                                                                             # 
  3  # Copyright (C) 2003-2013 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.gui import Gui 
 35  from data.pipe_container import PipeContainer 
 36  from data.relax_xml import fill_object_contents, xml_to_object 
 37  import generic_fns 
 38  from relax_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 = generic_fns.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 generic_fns.mol_res_spin.name_spin(spin_id=spin_id, name='N', pipe=pipe_name) 226 elif search('C', spin.isotope): 227 generic_fns.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 = generic_fns.mol_res_spin.generate_spin_id_unique(pipe_cont=dp, mol=mol, res=res, spin=spin) 239 spin_id2 = generic_fns.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 = generic_fns.mol_res_spin.return_spin(spin_id2, pipe=pipe_name) 243 if h_spin: 244 spin_id2 = generic_fns.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 = 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) 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 generic_fns.mol_res_spin.set_spin_element(spin_id=spin_id2, element='H', pipe=pipe_name) 254 if not hasattr(h_spin, 'isotope'): 255 generic_fns.mol_res_spin.set_spin_isotope(spin_id=spin_id2, isotope='1H', pipe=pipe_name) 256 generic_fns.dipole_pair.define(spin_id1, spin_id2, verbose=False, pipe=pipe_name) 257 258 # Get the interatomic data container. 259 interatom = generic_fns.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
286 - def add(self, pipe_name, pipe_type, bundle=None, switch=True):
287 """Method for adding a new data pipe container to the dictionary. 288 289 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. 290 291 @param pipe_name: The name of the new data pipe. 292 @type pipe_name: str 293 @param pipe_type: The data pipe type. 294 @type pipe_type: str 295 @keyword bundle: The optional data pipe bundle to associate the data pipe with. 296 @type bundle: str or None 297 @keyword switch: A flag which if True will cause the new data pipe to be set to the current data pipe. 298 @type switch: bool 299 """ 300 301 # Test if the pipe already exists. 302 if pipe_name in list(self.instance.keys()): 303 raise RelaxPipeError(pipe_name) 304 305 # Create a new container. 306 self[pipe_name] = PipeContainer() 307 308 # Add the data pipe type string to the container. 309 self[pipe_name].pipe_type = pipe_type 310 311 # The pipe bundle. 312 if bundle: 313 # A new bundle. 314 if bundle not in list(self.pipe_bundles.keys()): 315 self.pipe_bundles[bundle] = [] 316 317 # Add the pipe to the bundle. 318 self.pipe_bundles[bundle].append(pipe_name) 319 320 # Change the current data pipe. 321 if switch: 322 # Set the current data pipe. 323 self.instance.current_pipe = pipe_name 324 builtins.cdp = self[pipe_name] 325 326 # Signal the switch. 327 status.observers.pipe_alteration.notify()
328 329
330 - def is_empty(self, verbosity=False):
331 """Method for testing if the relax data store is empty. 332 333 @keyword verbosity: A flag which if True will cause messages to be printed to STDERR. 334 @type verbosity: bool 335 @return: True if the data store is empty, False otherwise. 336 @rtype: bool 337 """ 338 339 # No pipes should exist. 340 if not list(self.keys()) == []: 341 if verbosity: 342 stderr.write("The relax data store contains the data pipes %s.\n" % list(self.keys())) 343 return False 344 345 # Objects which should be in here. 346 blacklist = [ 347 'pipe_bundles', 348 'relax_gui' 349 ] 350 351 # An object has been added to the data store. 352 for name in dir(self): 353 # Skip the data store methods. 354 if name in list(self.__class__.__dict__.keys()): 355 continue 356 357 # Skip the dict methods. 358 if name in list(dict.__dict__.keys()): 359 continue 360 361 # Skip special objects. 362 if search("^__", name): 363 continue 364 365 # Blacklisted objects to skip. 366 if name in blacklist: 367 continue 368 369 # An object has been added. 370 if verbosity: 371 stderr.write("The relax data store contains the object %s.\n" % name) 372 return False 373 374 # The data store is empty. 375 return True
376 377
378 - def from_xml(self, file, dir=None, pipe_to=None, verbosity=1):
379 """Parse a XML document representation of a data pipe, and load it into the relax data store. 380 381 @param file: The open file object. 382 @type file: file 383 @keyword dir: The name of the directory containing the results file (needed 384 for loading external files). 385 @type dir: str 386 @keyword pipe_to: The data pipe to load the XML data pipe into (the file must only 387 contain one data pipe). 388 @type pipe_to: str 389 @keyword verbosity: A flag specifying the amount of information to print. The 390 higher the value, the greater the verbosity. 391 @type verbosity: int 392 @raises RelaxError: If pipe_to is given and the file contains multiple pipe 393 elements; or if the data pipes in the XML file already exist in 394 the relax data store; or if the data pipe type is invalid; or 395 if the target data pipe is not empty. 396 @raises RelaxNoPipeError: If pipe_to is given but the data pipe does not exist. 397 @raises RelaxError: If the data pipes in the XML file already exist in the relax 398 data store, or if the data pipe type is invalid. 399 @raises RelaxPipeError: If the data pipes of the XML file are already present in the 400 relax data store. 401 """ 402 403 # Create the XML document from the file. 404 doc = xml.dom.minidom.parse(file) 405 406 # Get the relax node. 407 relax_node = doc.childNodes[0] 408 409 # Get the relax version of the XML file. 410 file_version = relax_node.getAttribute('file_version') 411 if file_version == '': 412 file_version = 1 413 else: 414 file_version = int(file_version) 415 416 # Get the GUI nodes. 417 gui_nodes = relax_node.getElementsByTagName('relax_gui') 418 if gui_nodes: 419 self.relax_gui.from_xml(gui_nodes[0], file_version=file_version) 420 421 # Recreate all the data store data structures. 422 xml_to_object(relax_node, self, file_version=file_version, blacklist=['pipe', 'relax_gui']) 423 424 # Get the pipe nodes. 425 pipe_nodes = relax_node.getElementsByTagName('pipe') 426 427 # Structure for the names of the new pipes. 428 pipes = [] 429 430 # Target loading to a specific pipe (for pipe results reading). 431 if pipe_to: 432 # Check if there are multiple pipes in the XML file. 433 if len(pipe_nodes) > 1: 434 raise RelaxError("The pipe_to target pipe argument '%s' cannot be given as the file contains multiple pipe elements." % pipe_to) 435 436 # The pipe type. 437 pipe_type = pipe_nodes[0].getAttribute('type') 438 439 # Check that the pipe already exists. 440 if not pipe_to in self: 441 raise RelaxNoPipeError(pipe_to) 442 443 # Check if the pipe type matches. 444 if pipe_type != self[pipe_to].pipe_type: 445 raise RelaxError("The XML file pipe type '%s' does not match the pipe type '%s'" % (pipe_type, self[pipe_to].pipe_type)) 446 447 # Check if the pipe is empty. 448 if not self[pipe_to].is_empty(): 449 raise RelaxError("The data pipe '%s' is not empty." % pipe_to) 450 451 # Load the data. 452 self[pipe_to].from_xml(pipe_nodes[0], dir=dir, file_version=file_version) 453 454 # Store the pipe name. 455 pipes.append(pipe_to) 456 457 # Load the state. 458 else: 459 # Checks. 460 for pipe_node in pipe_nodes: 461 # The pipe name and type. 462 pipe_name = str(pipe_node.getAttribute('name')) 463 pipe_type = pipe_node.getAttribute('type') 464 465 # Existence check. 466 if pipe_name in self: 467 raise RelaxPipeError(pipe_name) 468 469 # Valid type check. 470 if not pipe_type in generic_fns.pipes.VALID_TYPES: 471 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)) 472 473 # Load the data pipes. 474 for pipe_node in pipe_nodes: 475 # The pipe name and type. 476 pipe_name = str(pipe_node.getAttribute('name')) 477 pipe_type = pipe_node.getAttribute('type') 478 479 # Add the data pipe. 480 switch = False 481 if self.current_pipe == None: 482 switch = True 483 self.add(pipe_name, pipe_type, switch=switch) 484 485 # Fill the pipe. 486 self[pipe_name].from_xml(pipe_node, file_version=file_version, dir=dir) 487 488 # Store the pipe name. 489 pipes.append(pipe_name) 490 491 # Set the current pipe. 492 if self.current_pipe in list(self.keys()): 493 builtins.cdp = self[self.current_pipe] 494 495 # Finally update the molecule, residue, and spin metadata for each data pipe. 496 for pipe in pipes: 497 generic_fns.mol_res_spin.metadata_update(pipe=pipe) 498 499 # Backwards compatibility transformations. 500 self._back_compat_hook(file_version, pipes=pipes)
501 502
503 - def to_xml(self, file, pipes=None):
504 """Create a XML document representation of the current data pipe. 505 506 This method creates the top level XML document including all the information needed 507 about relax, calls the PipeContainer.xml_write() method to fill in the document contents, 508 and writes the XML into the file object. 509 510 @param file: The open file object. 511 @type file: file 512 @param pipes: The name of the pipe, or list of pipes to place in the XML file. 513 @type pipes: str or list of str 514 """ 515 516 # The pipes to include in the XML file. 517 all = False 518 if not pipes: 519 all = True 520 pipes = list(self.keys()) 521 elif isinstance(pipes, str): 522 pipes = [pipes] 523 524 # Sort the pipes. 525 pipes.sort() 526 527 # Create the XML document object. 528 xmldoc = xml.dom.minidom.Document() 529 530 # Create the top level element, including the relax URL. 531 top_element = xmldoc.createElementNS('http://www.nmr-relax.com', 'relax') 532 top_element.setAttribute("xmlns", "http://www.nmr-relax.com") 533 534 # Append the element. 535 xmldoc.appendChild(top_element) 536 537 # Set the relax version number, and add a creation time. 538 top_element.setAttribute('version', version.version) 539 top_element.setAttribute('time', asctime()) 540 top_element.setAttribute('file_version', "2") 541 rev = version.revision() 542 if rev: 543 top_element.setAttribute('revision', rev) 544 url = version.url() 545 if url: 546 top_element.setAttribute('url', url) 547 548 # Add all objects in the data store base object to the XML element. 549 if all: 550 blacklist = list(list(self.__class__.__dict__.keys()) + list(dict.__dict__.keys())) 551 for name in dir(self): 552 # Skip blacklisted objects. 553 if name in blacklist: 554 continue 555 556 # Skip special objects. 557 if search('^_', name): 558 continue 559 560 # Execute any to_xml() methods, and add that object to the blacklist. 561 obj = getattr(self, name) 562 if hasattr(obj, 'to_xml'): 563 obj.to_xml(xmldoc, top_element) 564 blacklist = blacklist + [name] 565 566 # Remove the current data pipe from the blacklist! 567 blacklist.remove('current_pipe') 568 569 # Add all simple python objects within the store. 570 fill_object_contents(xmldoc, top_element, object=self, blacklist=blacklist) 571 572 # Loop over the pipes. 573 for pipe in pipes: 574 # Create the pipe XML element and add it to the top level XML element. 575 pipe_element = xmldoc.createElement('pipe') 576 top_element.appendChild(pipe_element) 577 578 # Set the data pipe attributes. 579 pipe_element.setAttribute('desc', 'The contents of a relax data pipe') 580 pipe_element.setAttribute('name', pipe) 581 pipe_element.setAttribute('type', self[pipe].pipe_type) 582 583 # Fill the data pipe XML element. 584 self[pipe].to_xml(xmldoc, pipe_element) 585 586 # Write out the XML file. 587 file.write(xmldoc.toprettyxml(indent=' '))
588