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