Package data_store :: Module mol_res_spin
[hide private]
[frames] | no frames]

Source Code for Module data_store.mol_res_spin

  1  ############################################################################### 
  2  #                                                                             # 
  3  # Copyright (C) 2007-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  """The molecule-residue-spin containers of the relax data store.""" 
 24   
 25  # Python module imports. 
 26  from re import match 
 27   
 28  # relax module imports. 
 29  from data_store.prototype import Prototype 
 30  from data_store.relax_xml import fill_object_contents, object_to_xml, xml_to_object 
 31  import pipe_control 
 32  from lib.errors import RelaxError, RelaxFromXMLNotEmptyError, RelaxImplementError 
 33  import specific_analyses 
 34   
 35   
 36  # The spin system data. 
 37  ####################### 
 38   
39 -class SpinContainer(Prototype):
40 """Class containing all the spin system specific data.""" 41
42 - def __init__(self, spin_name=None, spin_num=None, select=True):
43 """Set up the default objects of the spin system data container.""" 44 45 # The spin system name and number. 46 self.name = spin_name 47 self.num = spin_num 48 self.select = select 49 50 # The private metadata. 51 self._mol_name = None 52 self._mol_index = None 53 self._res_name = None 54 self._res_num = None 55 self._res_index = None 56 self._spin_index = None 57 self._spin_ids = []
58 59
60 - def __repr__(self):
61 """The string representation of the object. 62 63 Rather than using the standard Python conventions (either the string representation of the 64 value or the "<...desc...>" notation), a rich-formatted description of the object is given. 65 """ 66 67 # Intro. 68 text = "Class containing all the spin system specific data.\n\n" 69 70 # Objects. 71 text = text + "\n" 72 text = text + "Objects:\n" 73 for name in dir(self): 74 # Skip the SpinContainer methods. 75 if name == 'is_empty': 76 continue 77 78 # Skip special objects. 79 if match("^_", name): 80 continue 81 82 # Add the object's attribute to the text string. 83 text = text + " " + name + ": " + repr(getattr(self, name)) + "\n" 84 85 return text
86 87
88 - def _back_compat_hook(self, file_version=None):
89 """Method for converting old spin data structures to the new ones. 90 91 @keyword file_version: The relax XML version of the XML file. 92 @type file_version: int 93 """ 94 95 # Model-free parameters. 96 if hasattr(self, 'equation') and self.equation in ['mf_orig', 'mf_ext', 'mf_ext2']: 97 self._back_compat_hook_mf_data() 98 99 # Relaxation data. 100 self._back_compat_hook_ri_data()
101 102
104 """Converting the old model-free parameter vector to the new one.""" 105 106 # Nothing to do. 107 if not hasattr(self, 'params'): 108 return 109 110 # Loop over the parameters, converting them to lowercase. 111 for i in range(len(self.params)): 112 self.params[i] = self.params[i].lower()
113 114
116 """Converting the old spin relaxation data structures to the new ones.""" 117 118 # Nothing to do. 119 if not (hasattr(self, 'frq_labels') and hasattr(self, 'noe_r1_table') and hasattr(self, 'remap_table')): 120 return 121 122 # Initialise the new structures. 123 self.ri_data = {} 124 self.ri_data_err = {} 125 sims = False 126 if hasattr(self, 'relax_sim_data'): 127 sims = True 128 self.ri_data_sim = {} 129 130 # Generate the new structures. 131 for i in range(self.num_ri): 132 # The ID. 133 ri_id = "%s_%s" % (self.ri_labels[i], self.frq_labels[self.remap_table[i]]) 134 135 # The relaxation data. 136 self.ri_data[ri_id] = self.relax_data[i] 137 self.ri_data_err[ri_id] = self.relax_error[i] 138 139 # Simulation data. 140 if sims: 141 self.ri_data_sim[ri_id] = [] 142 for j in range(cdp.sim_number): 143 self.ri_data_sim[ri_id].append(self.relax_sim_data[j][i]) 144 145 # Delete the old structures. 146 del self.frq 147 del self.frq_labels 148 del self.noe_r1_table 149 del self.num_frq 150 del self.num_ri 151 del self.ri_labels 152 del self.remap_table 153 del self.relax_data 154 del self.relax_error 155 if sims: 156 del self.relax_sim_data
157 158
159 - def is_empty(self):
160 """Method for testing if this SpinContainer object is empty. 161 162 @return: True if this container is empty and the spin number and name have not been set, 163 False otherwise. 164 @rtype: bool 165 """ 166 167 # The spin number or spin name has been set. 168 if self.num != None or self.name != None: 169 return False 170 171 # An object has been added to the container. 172 for name in dir(self): 173 # Skip the objects initialised in __init__(). 174 if name == 'num' or name == 'name' or name == 'select': 175 continue 176 177 # Skip the SpinContainer methods. 178 if name == 'is_empty': 179 continue 180 181 # Skip special objects. 182 if match("^_", name): 183 continue 184 185 # An object has been added. 186 return False 187 188 # The SpinContainer is unmodified. 189 return True
190 191
192 -class SpinList(list):
193 """List type data container for spin system specific data.""" 194
195 - def __init__(self):
196 """Set up the first spin system data container.""" 197 198 # Add the initial spin system container at index 0. 199 self.append(SpinContainer())
200 201
202 - def __repr__(self):
203 """The string representation of the object. 204 205 Rather than using the standard Python conventions (either the string representation of the 206 value or the "<...desc...>" notation), a rich-formatted description of the object is given. 207 """ 208 209 # Intro. 210 text = "Spin systems.\n\n" 211 212 # Residue data. 213 text = text + "%-8s%-8s%-8s%-10s" % ("Index", "Number", "Name", "Selected") + "\n" 214 for i in range(len(self)): 215 text = text + "%-8i%-8s%-8s%-10s" % (i, repr(self[i].num), self[i].name, self[i].select) + "\n" 216 text = text + "\nThese can be accessed by typing 'D.mol[i].res[j].spin[k]', where D is the relax data storage object.\n" 217 218 return text
219 220
221 - def add_item(self, spin_name=None, spin_num=None, select=True):
222 """Appending an empty container to the list. 223 224 @keyword spin_name: The name of the new spin. 225 @type spin_name: str or None 226 @keyword spin_num: The number of the new spin. 227 @type spin_num: str or None 228 @keyword select: The selection flag. 229 @type select: bool 230 @return: The new container. 231 @rtype: SpinContainer instance 232 """ 233 234 # If no spin data exists, replace the empty first spin with this spin. 235 if self.is_empty(): 236 self[0].num = spin_num 237 self[0].name = spin_name 238 self[0].select = select 239 240 # Return the container. 241 return self[0] 242 243 # Otherwise append a new SpinContainer. 244 else: 245 # Test if the spin number (or name if unnumbered) already exists. 246 for i in range(len(self)): 247 # Spin number has been supplied. 248 if spin_num != None and spin_name != None: 249 if self[i].num == spin_num and self[i].name == spin_name: 250 raise RelaxError("The spin with name '%s' and number '%s' already exists." % (spin_name, spin_num)) 251 252 # No spin numbers. 253 if spin_num == None and self[i].name == spin_name: 254 raise RelaxError("The unnumbered spin name '%s' already exists." % spin_name) 255 256 # Append a new SpinContainer. 257 self.append(SpinContainer(spin_name, spin_num, select)) 258 259 # Return the container. 260 return self[-1]
261 262
263 - def is_empty(self):
264 """Method for testing if this SpinList object is empty. 265 266 @return: True if this list only has one SpinContainer and the spin number and name have 267 not been set, False otherwise. 268 @rtype: bool 269 """ 270 271 # There is only one SpinContainer and it is empty. 272 if len(self) == 1 and self[0].is_empty(): 273 return True 274 275 # Otherwise. 276 return False
277 278
279 - def from_xml(self, spin_nodes, file_version=None):
280 """Recreate a spin list data structure from the XML spin nodes. 281 282 @param spin_nodes: The spin XML nodes. 283 @type spin_nodes: xml.dom.minicompat.NodeList instance 284 @keyword file_version: The relax XML version of the XML file. 285 @type file_version: int 286 """ 287 288 # Test if empty. 289 if not self.is_empty(): 290 raise RelaxFromXMLNotEmptyError(self.__class__.__name__) 291 292 # Loop over the spins. 293 for spin_node in spin_nodes: 294 # Get the spin details and add the spin to the SpinList structure. 295 name = str(spin_node.getAttribute('name')) 296 if name == 'None': 297 name = None 298 num = eval(spin_node.getAttribute('num')) 299 self.add_item(spin_name=name, spin_num=num) 300 301 # Recreate the current spin container. 302 xml_to_object(spin_node, self[-1], file_version=file_version) 303 304 # Backwards compatibility transformations. 305 self[-1]._back_compat_hook(file_version)
306 307
308 - def to_xml(self, doc, element, pipe_type=None):
309 """Create XML elements for each spin. 310 311 @param doc: The XML document object. 312 @type doc: xml.dom.minidom.Document instance 313 @param element: The element to add the spin XML elements to. 314 @type element: XML element object 315 @keyword pipe_type: The type of the pipe being converted to XML. 316 @type pipe_type: str 317 """ 318 319 # Get the specific functions. 320 data_names = specific_analyses.setup.get_specific_fn('data_names', pipe_type, raise_error=False) 321 return_data_desc = specific_analyses.setup.get_specific_fn('return_data_desc', pipe_type, raise_error=False) 322 323 # Loop over the spins. 324 for i in range(len(self)): 325 # Create an XML element for this spin and add it to the higher level element. 326 spin_element = doc.createElement('spin') 327 element.appendChild(spin_element) 328 329 # Set the spin attributes. 330 spin_element.setAttribute('desc', 'Spin container') 331 spin_element.setAttribute('name', str(self[i].name)) 332 spin_element.setAttribute('num', str(self[i].num)) 333 334 # Get the spin specific object names and loop over them to get their descriptions. 335 object_info = [] 336 try: 337 for name in data_names(error_names=True, sim_names=True): 338 # Get the description. 339 if return_data_desc: 340 desc = return_data_desc(name) 341 else: 342 desc = None 343 344 # Append the two. 345 object_info.append([name, desc]) 346 except RelaxImplementError: 347 pass 348 349 # Add the ordered objects. 350 blacklist = [] 351 for name, desc in object_info: 352 # Add the name to the blacklist. 353 blacklist.append(name) 354 355 # Skip the object if it is missing from the SpinContainer. 356 if not hasattr(self[i], name): 357 continue 358 359 # Create a new element for this object, and add it to the main element. 360 sub_element = doc.createElement(name) 361 spin_element.appendChild(sub_element) 362 363 # Add the object description. 364 if desc: 365 sub_element.setAttribute('desc', desc) 366 367 # Get the object. 368 object = getattr(self[i], name) 369 370 # Convert to XML. 371 object_to_xml(doc, sub_element, value=object) 372 373 # Add all simple python objects within the SpinContainer to the XML element. 374 fill_object_contents(doc, spin_element, object=self[i], blacklist=['name', 'num', 'spin'] + blacklist + list(self[i].__class__.__dict__.keys()))
375 376 377 378 # The residue data. 379 ################### 380
381 -class ResidueContainer(Prototype):
382 """Class containing all the residue specific data.""" 383
384 - def __init__(self, res_name=None, res_num=None):
385 """Set up the default objects of the residue data container.""" 386 387 # The residue name and number. 388 self.name = res_name 389 self.num = res_num 390 391 # The private metadata. 392 self._mol_name = None 393 self._mol_index = None 394 self._res_index = None 395 396 # The empty spin system list. 397 self.spin = SpinList()
398 399
400 - def __repr__(self):
401 """The string representation of the object. 402 403 Rather than using the standard Python conventions (either the string representation of the 404 value or the "<...desc...>" notation), a rich-formatted description of the object is given. 405 """ 406 407 # Intro. 408 text = "Class containing all the residue specific data.\n" 409 410 # Objects. 411 text = text + "\n" 412 text = text + "Objects:\n" 413 for name in dir(self): 414 # Spin systems. 415 if name == 'spin': 416 text = text + " spin: The list of spin systems of the residues\n" 417 continue 418 419 # Skip the ResidueContainer methods. 420 if name == 'is_empty': 421 continue 422 423 # Skip special objects. 424 if match("^_", name): 425 continue 426 427 # Add the object's attribute to the text string. 428 text = text + " " + name + ": " + repr(getattr(self, name)) + "\n" 429 430 return text
431 432
433 - def is_empty(self):
434 """Method for testing if this ResidueContainer object is empty. 435 436 @return: True if this container is empty and the residue number and name have not been 437 set, False otherwise. 438 @rtype: bool 439 """ 440 441 # The residue number or residue name have been set. 442 if self.num != None or self.name != None: 443 return False 444 445 # An object has been added to the container. 446 for name in dir(self): 447 # Skip the objects initialised in __init__(). 448 if name == 'num' or name == 'name' or name == 'spin': 449 continue 450 451 # Skip the ResidueContainer methods. 452 if name == 'is_empty': 453 continue 454 455 # Skip special objects. 456 if match("^_", name): 457 continue 458 459 # An object has been added. 460 return False 461 462 # The spin list is not empty. 463 if not self.spin.is_empty(): 464 return False 465 466 # The ResidueContainer is unmodified. 467 return True
468 469
470 -class ResidueList(list):
471 """List type data container for residue specific data.""" 472
473 - def __init__(self):
474 """Set up the first residue data container.""" 475 476 # Add the initial residue container at index 0. 477 self.append(ResidueContainer())
478 479
480 - def __repr__(self):
481 """The string representation of the object. 482 483 Rather than using the standard Python conventions (either the string representation of the 484 value or the "<...desc...>" notation), a rich-formatted description of the object is given. 485 """ 486 487 # Intro. 488 text = "Residues.\n\n" 489 490 # Residue data. 491 text = text + "%-8s%-8s%-8s" % ("Index", "Number", "Name") + "\n" 492 for i in range(len(self)): 493 text = text + "%-8i%-8s%-8s" % (i, repr(self[i].num), self[i].name) + "\n" 494 text = text + "\nThese can be accessed by typing 'D.mol[i].res[j]', where D is the relax data storage object.\n" 495 496 return text
497 498
499 - def add_item(self, res_name=None, res_num=None):
500 """Append an empty ResidueContainer to the ResidueList.""" 501 502 # If no residue data exists, replace the empty first residue with this residue. 503 if self.is_empty(): 504 self[0].num = res_num 505 self[0].name = res_name 506 507 # Otherwise append a new ResidueContainer. 508 else: 509 # Test if the residue number (or name if unnumbered) already exists. 510 for i in range(len(self)): 511 # Residue number has been supplied. 512 if res_num != None: 513 if self[i].num == res_num: 514 raise RelaxError("The residue number '" + repr(res_num) + "' already exists in the sequence.") 515 516 # No residue numbers. 517 else: 518 if self[i].name == res_name: 519 raise RelaxError("The unnumbered residue name '" + repr(res_name) + "' already exists.") 520 521 # Append a new ResidueContainer. 522 self.append(ResidueContainer(res_name, res_num))
523 524
525 - def is_empty(self):
526 """Method for testing if this ResidueList object is empty. 527 528 @return: True if this list only has one ResidueContainer and the residue number and name 529 have not been set, False otherwise. 530 @rtype: bool 531 """ 532 533 # There is only one ResidueContainer and it is empty. 534 if len(self) == 1 and self[0].is_empty(): 535 return True 536 537 # Otherwise. 538 return False
539 540
541 - def from_xml(self, res_nodes, file_version=None):
542 """Recreate a residue list data structure from the XML residue nodes. 543 544 @param res_nodes: The residue XML nodes. 545 @type res_nodes: xml.dom.minicompat.NodeList instance 546 @keyword file_version: The relax XML version of the XML file. 547 @type file_version: int 548 """ 549 550 # Test if empty. 551 if not self.is_empty(): 552 raise RelaxFromXMLNotEmptyError(self.__class__.__name__) 553 554 # Loop over the residues. 555 for res_node in res_nodes: 556 # Get the residue details and add the residue to the ResidueList structure. 557 name = str(res_node.getAttribute('name')) 558 if name == 'None': 559 name = None 560 num = eval(res_node.getAttribute('num')) 561 self.add_item(res_name=name, res_num=num) 562 563 # Get the spin nodes. 564 spin_nodes = res_node.getElementsByTagName('spin') 565 566 # Recreate the spin data structures for the current residue. 567 self[-1].spin.from_xml(spin_nodes, file_version=file_version)
568 569
570 - def to_xml(self, doc, element, pipe_type=None):
571 """Create XML elements for each residue. 572 573 @param doc: The XML document object. 574 @type doc: xml.dom.minidom.Document instance 575 @param element: The element to add the residue XML elements to. 576 @type element: XML element object 577 @keyword pipe_type: The type of the pipe being converted to XML. 578 @type pipe_type: str 579 """ 580 581 # Loop over the residues. 582 for i in range(len(self)): 583 # Create an XML element for this residue and add it to the higher level element. 584 res_element = doc.createElement('res') 585 element.appendChild(res_element) 586 587 # Set the residue attributes. 588 res_element.setAttribute('desc', 'Residue container') 589 res_element.setAttribute('name', str(self[i].name)) 590 res_element.setAttribute('num', str(self[i].num)) 591 592 # Add all simple python objects within the ResidueContainer to the XML element. 593 fill_object_contents(doc, res_element, object=self[i], blacklist=['name', 'num', 'spin'] + list(self[i].__class__.__dict__.keys())) 594 595 # Add the residue data. 596 self[i].spin.to_xml(doc, res_element, pipe_type=pipe_type)
597 598 599 600 # The molecule data. 601 ################### 602
603 -class MoleculeContainer(Prototype):
604 """Class containing all the molecule specific data.""" 605
606 - def __init__(self, mol_name=None, mol_type=None):
607 """Set up the default objects of the molecule data container.""" 608 609 # The name of the molecule, corresponding to that of the structure file if specified. 610 self.name = mol_name 611 612 # The type of molecule. 613 self.type = mol_type 614 615 # The private metadata. 616 self._mol_index = None 617 618 # The empty residue list. 619 self.res = ResidueList()
620 621
622 - def __repr__(self):
623 """The string representation of the object. 624 625 Rather than using the standard Python conventions (either the string representation of the 626 value or the "<...desc...>" notation), a rich-formatted description of the object is given. 627 """ 628 629 # Intro. 630 text = "Class containing all the molecule specific data.\n" 631 632 # Objects. 633 text = text + "\n" 634 text = text + "Objects:\n" 635 for name in dir(self): 636 # Residue list. 637 if name == 'res': 638 text = text + " res: The list of the residues of the molecule\n" 639 continue 640 641 # Skip the MoleculeContainer methods. 642 if name == 'is_empty': 643 continue 644 645 # Skip special objects. 646 if match("^_", name): 647 continue 648 649 # Add the object's attribute to the text string. 650 text = text + " " + name + ": " + repr(getattr(self, name)) + "\n" 651 652 return text
653 654
655 - def is_empty(self):
656 """Method for testing if this MoleculeContainer object is empty. 657 658 @return: True if this container is empty and the molecule name has not been set, False 659 otherwise. 660 @rtype: bool 661 """ 662 663 # The molecule name has been set. 664 if self.name != None: 665 return False 666 667 # An object has been added to the container. 668 for name in dir(self): 669 # Skip the objects initialised in __init__(). 670 if name in ['name', 'res', 'type']: 671 continue 672 673 # Skip the MoleculeContainer methods. 674 if name == 'is_empty': 675 continue 676 677 # Skip special objects. 678 if match("^_", name): 679 continue 680 681 # An object has been added. 682 return False 683 684 # The residue list is not empty. 685 if not self.res.is_empty(): 686 return False 687 688 # The MoleculeContainer is unmodified. 689 return True
690 691
692 -class MoleculeList(list):
693 """List type data container for the molecule specific data.""" 694
695 - def __init__(self):
696 """Set up the first molecule data container.""" 697 698 # Add the initial molecule container at index 0. 699 self.append(MoleculeContainer()) 700 701 # Create a special private lookup table for fast spin accesses. 702 self._spin_id_lookup = {}
703 704
705 - def __repr__(self):
706 """The string representation of the object. 707 708 Rather than using the standard Python conventions (either the string representation of the 709 value or the "<...desc...>" notation), a rich-formatted description of the object is given. 710 """ 711 712 text = "Molecules.\n\n" 713 text = text + "%-8s%-8s" % ("Index", "Name") + "\n" 714 for i in range(len(self)): 715 text = text + "%-8i%-8s" % (i, self[i].name) + "\n" 716 text = text + "\nThese can be accessed by typing 'D.mol[i]', where D is the relax data storage object.\n" 717 return text
718 719
720 - def add_item(self, mol_name=None, mol_type=None):
721 """Append an empty MoleculeContainer to the MoleculeList.""" 722 723 # If no molecule data exists, replace the empty first molecule with this molecule (just a renaming). 724 if self.is_empty(): 725 self[0].name = mol_name 726 self[0].type = mol_type 727 728 # Otherwise append an empty MoleculeContainer. 729 else: 730 # Test if the molecule name already exists. 731 for i in range(len(self)): 732 if self[i].name == mol_name: 733 raise RelaxError("The molecule '%s' already exists in the sequence." % mol_name) 734 735 # Append an empty MoleculeContainer. 736 self.append(MoleculeContainer(mol_name, mol_type))
737 738
739 - def is_empty(self):
740 """Method for testing if this MoleculeList object is empty. 741 742 @return: True if this list only has one MoleculeContainer and the molecule name has not 743 been set, False otherwise. 744 @rtype: bool 745 """ 746 747 # There is only one MoleculeContainer and it is empty. 748 if len(self) == 1 and self[0].is_empty(): 749 return True 750 751 # Otherwise. 752 return False
753 754
755 - def from_xml(self, mol_nodes, file_version=None):
756 """Recreate a molecule list data structure from the XML molecule nodes. 757 758 @param mol_nodes: The molecule XML nodes. 759 @type mol_nodes: xml.dom.minicompat.NodeList instance 760 @keyword file_version: The relax XML version of the XML file. 761 @type file_version: int 762 """ 763 764 # Test if empty. 765 if not self.is_empty(): 766 raise RelaxFromXMLNotEmptyError(self.__class__.__name__) 767 768 # Loop over the molecules. 769 for mol_node in mol_nodes: 770 # Get the molecule details and add the molecule to the MoleculeList structure. 771 name = str(mol_node.getAttribute('name')) 772 if name == 'None': 773 name = None 774 type = str(mol_node.getAttribute('type')) 775 if type == 'None': 776 type = None 777 self.add_item(mol_name=name, mol_type=type) 778 779 # Get the residue nodes. 780 res_nodes = mol_node.getElementsByTagName('res') 781 782 # Recreate the residue data structures for the current molecule. 783 self[-1].res.from_xml(res_nodes, file_version=file_version)
784 785
786 - def to_xml(self, doc, element, pipe_type=None):
787 """Create XML elements for each molecule. 788 789 @param doc: The XML document object. 790 @type doc: Xml.dom.minidom.Document instance 791 @param element: The element to add the molecule XML elements to. 792 @type element: XML element object 793 @keyword pipe_type: The type of the pipe being converted to XML. 794 @type pipe_type: str 795 """ 796 797 # Loop over the molecules. 798 for i in range(len(self)): 799 # Create an XML element for this molecule and add it to the higher level element. 800 mol_element = doc.createElement('mol') 801 element.appendChild(mol_element) 802 803 # Set the molecule attributes. 804 mol_element.setAttribute('desc', 'Molecule container') 805 mol_element.setAttribute('name', str(self[i].name)) 806 mol_element.setAttribute('type', str(self[i].type)) 807 808 # Add all simple python objects within the MoleculeContainer to the XML element. 809 fill_object_contents(doc, mol_element, object=self[i], blacklist=['name', 'res', 'type'] + list(self[i].__class__.__dict__.keys())) 810 811 # Add the residue data. 812 self[i].res.to_xml(doc, mol_element, pipe_type=pipe_type)
813