Package generic_fns :: Package structure :: Module api_base
[hide private]
[frames] | no frames]

Source Code for Module generic_fns.structure.api_base

   1  ############################################################################### 
   2  #                                                                             # 
   3  # Copyright (C) 2008-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  """The API for accessing, creating, and modifying structural information. 
  24   
  25  The full API is documented within this base class object.  Each available API call is given as a 
  26  prototype method stub (or functional method) with all arguments, raised errors, and return values 
  27  documented. 
  28  """ 
  29   
  30  # Python module imports. 
  31  from numpy import float64 
  32  from os import sep 
  33  from re import match 
  34  from types import MethodType 
  35  from warnings import warn 
  36   
  37  # relax module import. 
  38  from data.relax_xml import fill_object_contents, object_to_xml, xml_to_object 
  39  from float import floatAsByteArray, packBytesAsPyFloat 
  40  from generic_fns.structure.superimpose import kabsch 
  41  from relax_errors import RelaxError, RelaxFileError, RelaxFromXMLNotEmptyError, RelaxImplementError 
  42  from relax_io import file_root 
  43  from relax_warnings import RelaxWarning 
  44   
  45   
46 -class Base_struct_API:
47 """The structural object base class. 48 49 All API methods are prototyped here as stub methods. 50 """ 51 52 # Identification string. 53 id = 'API' 54 55
56 - def __init__(self):
57 """Initialise the structural object.""" 58 59 # Initialise the empty model list. 60 self.structural_data = ModelList()
61 62
63 - def add_atom(self, mol_name=None, atom_name=None, res_name=None, res_num=None, pos=[None, None, None], element=None, atom_num=None, chain_id=None, segment_id=None, pdb_record=None):
64 """Add a new atom to the structural data object. 65 66 @keyword mol_name: The name of the molecule. 67 @type mol_name: str 68 @keyword atom_name: The atom name, e.g. 'H1'. 69 @type atom_name: str or None 70 @keyword res_name: The residue name. 71 @type res_name: str or None 72 @keyword res_num: The residue number. 73 @type res_num: int or None 74 @keyword pos: The position vector of coordinates. 75 @type pos: list (length = 3) 76 @keyword element: The element symbol. 77 @type element: str or None 78 @keyword atom_num: The atom number. 79 @type atom_num: int or None 80 @keyword chain_id: The chain identifier. 81 @type chain_id: str or None 82 @keyword segment_id: The segment identifier. 83 @type segment_id: str or None 84 @keyword pdb_record: The optional PDB record name, e.g. 'ATOM' or 'HETATM'. 85 @type pdb_record: str or None 86 """ 87 88 # Raise the error. 89 raise RelaxImplementError
90 91
92 - def add_model(self, model=None, coords_from=None):
93 """Add a new model to the store. 94 95 The new model will be constructured with the structural information from the other models currently present. The coords_from argument allows the atomic positions to be taken from a certain model. If this argument is not set, then the atomic positions from the first model will be used. 96 97 @keyword model: The number of the model to create. 98 @type model: int or None 99 @keyword coords_from: The model number to take the coordinates from. 100 @type coords_from: int or None 101 @return: The model container. 102 @rtype: ModelContainer instance 103 """ 104 105 # Raise the error. 106 raise RelaxImplementError
107 108
109 - def add_molecule(self, name=None):
110 """Prototype method stub for adding the given molecule to the store. 111 112 @keyword name: The molecule identification string. 113 @type name: str 114 """ 115 116 # Raise the error. 117 raise RelaxImplementError
118 119
120 - def are_bonded(self, atom_id1=None, atom_id2=None):
121 """Prototype method stub for determining if two atoms are directly bonded to each other. 122 123 @keyword atom_id1: The molecule, residue, and atom identifier string of the first atom. 124 @type atom_id1: str 125 @keyword atom_id2: The molecule, residue, and atom identifier string of the second atom. 126 @type atom_id2: str 127 @return: True if the atoms are directly bonded. 128 @rtype: bool 129 """ 130 131 # Raise the error. 132 raise RelaxImplementError('are_bonded')
133 134
135 - def atom_loop(self, atom_id=None, str_id=None, model_num=None, model_num_flag=False, mol_name_flag=False, res_num_flag=False, res_name_flag=False, atom_num_flag=False, atom_name_flag=False, element_flag=False, pos_flag=False, index_flag=False, ave=False):
136 """Prototype generator method stub for looping over all atoms in the structural data object. 137 138 This method should be designed as a U{generator<http://www.python.org/dev/peps/pep-0255/>}. It should loop over all atoms of the system yielding the following atomic information, if the corresponding flag is True, in tuple form: 139 140 1. Model number. 141 2. Molecule name. 142 3. Residue number. 143 4. Residue name. 144 5. Atom number. 145 6. Atom name. 146 7. The element name (its atomic symbol and optionally the isotope, e.g. 'N', 'Mg', '17O', '13C', etc). 147 8. The position of the atom in Euclidean space. 148 149 150 @keyword atom_id: The molecule, residue, and atom identifier string. Only atoms matching this selection will be yielded. 151 @type atom_id: str 152 @keyword str_id: The structure identifier. This can be the file name, model number, or structure number. If None, then all structures will be looped over. 153 @type str_id: str, int, or None 154 @keyword model_num: Only loop over a specific model. 155 @type model_num: int or None 156 @keyword model_num_flag: A flag which if True will cause the model number to be yielded. 157 @type model_num_flag: bool 158 @keyword mol_name_flag: A flag which if True will cause the molecule name to be yielded. 159 @type mol_name_flag: bool 160 @keyword res_num_flag: A flag which if True will cause the residue number to be yielded. 161 @type res_num_flag: bool 162 @keyword res_name_flag: A flag which if True will cause the residue name to be yielded. 163 @type res_name_flag: bool 164 @keyword atom_num_flag: A flag which if True will cause the atom number to be yielded. 165 @type atom_num_flag: bool 166 @keyword atom_name_flag: A flag which if True will cause the atom name to be yielded. 167 @type atom_name_flag: bool 168 @keyword element_flag: A flag which if True will cause the element name to be yielded. 169 @type element_flag: bool 170 @keyword pos_flag: A flag which if True will cause the atomic position to be yielded. 171 @type pos_flag: bool 172 @keyword index_flag: A flag which if True will cause the atomic index to be yielded. 173 @type index_flag: bool 174 @keyword ave: A flag which if True will result in this method returning the average atom properties across all loaded structures. 175 @type ave: bool 176 @return: A tuple of atomic information, as described in the docstring. 177 @rtype: tuple consisting of optional molecule name (str), residue number (int), residue name (str), atom number (int), atom name(str), element name (str), and atomic position (array of len 3). 178 """ 179 180 # Raise the error. 181 raise RelaxImplementError
182 183
184 - def attached_atom(self, atom_id=None, attached_atom=None, model=None):
185 """Prototype method stub for finding the atom 'attached_atom' bonded to the atom 'atom_id'. 186 187 @keyword atom_id: The molecule, residue, and atom identifier string. This must 188 correspond to a single atom in the system. 189 @type atom_id: str 190 @keyword attached_atom: The name of the attached atom to return. 191 @type attached_atom: str 192 @keyword model: The model to return the positional information from. If not 193 supplied and multiple models exist, then the returned atomic 194 position will be a list of the positions in each model. 195 @type model: None or int 196 @return: A tuple of information about the bonded atom. 197 @rtype: tuple consisting of the atom number (int), atom name (str), element 198 name (str), and atomic positions for each model (list of numpy 199 arrays) 200 """ 201 202 # Raise the error. 203 raise RelaxImplementError
204 205
206 - def bond_vectors(self, attached_atom=None, model_num=None, mol_name=None, res_num=None, res_name=None, spin_num=None, spin_name=None, return_name=False, return_warnings=False):
207 """Prototype method stub for finding the bond vectors between 'attached_atom' and 'atom_id'. 208 209 @keyword attached_atom: The name of the bonded atom. 210 @type attached_atom: str 211 @keyword model_num: The model of which to return the vectors from. If not supplied 212 and multiple models exist, then vectors from all models will be 213 returned. 214 @type model_num: None or int 215 @keyword mol_name: The name of the molecule that attached_atom belongs to. 216 @type mol_name: str 217 @keyword res_num: The number of the residue that attached_atom belongs to. 218 @type res_num: str 219 @keyword res_name: The name of the residue that attached_atom belongs to. 220 @type res_name: str 221 @keyword spin_num: The number of the spin that attached_atom is attached to. 222 @type spin_num: str 223 @keyword spin_name: The name of the spin that attached_atom is attached to. 224 @type spin_name: str 225 @keyword return_name: A flag which if True will cause the name of the attached atom to 226 be returned together with the bond vectors. 227 @type return_name: bool 228 @keyword return_warnings: A flag which if True will cause warning messages to be returned. 229 @type return_warnings: bool 230 @return: The list of bond vectors for each model. 231 @rtype: list of numpy arrays 232 """ 233 234 # Raise the error. 235 raise RelaxImplementError
236 237
238 - def connect_atom(self, mol_name=None, index1=None, index2=None):
239 """Connect two atoms in the structural data object. 240 241 @keyword mol_name: The name of the molecule. 242 @type mol_name: str 243 @keyword index1: The global index of the first atom. 244 @type index1: str 245 @keyword index2: The global index of the first atom. 246 @type index2: str 247 """ 248 249 # Raise the error. 250 raise RelaxImplementError
251 252
253 - def delete(self, atom_id=None):
254 """Prototype method stub for deleting structural data from the current data pipe. 255 256 @keyword atom_id: The molecule, residue, and atom identifier string. This matches the spin ID string format. If not given, then all structural data will be deleted. 257 @type atom_id: str or None 258 """ 259 260 # Raise the error. 261 raise RelaxImplementError
262 263
264 - def empty(self):
265 """Report if the structural data structure is empty or not. 266 267 @return: True if empty, False otherwise. 268 @rtype: bool 269 """ 270 271 # Check the ModelList structure. 272 if len(self.structural_data) == 0: 273 return True 274 else: 275 return False
276 277
278 - def from_xml(self, str_node, dir=None, id=None, file_version=1):
279 """Recreate the structural object from the XML structural object node. 280 281 @param str_node: The structural object XML node. 282 @type str_node: xml.dom.minicompat.Element instance 283 @keyword dir: The name of the directory containing the results file. 284 @type dir: str 285 @keyword id: The specific structural object ID string. This can be 'scientific', 'internal', etc. 286 @type id: str 287 @keyword file_version: The relax XML version of the XML file. 288 @type file_version: int 289 """ 290 291 # Recreate all base objects (i.e. metadata). 292 xml_to_object(str_node, self, file_version=file_version, blacklist=['model', 'displacements']) 293 294 # Recreate the model / molecule data structure. 295 model_nodes = str_node.getElementsByTagName('model') 296 self.structural_data.from_xml(model_nodes, id=id, file_version=file_version) 297 298 # The displacement structure. 299 disp_nodes = str_node.getElementsByTagName('displacements') 300 if len(disp_nodes): 301 # Initialise the object. 302 self.displacements = Displacements() 303 304 # Recreate the molecule data structures for the current model. 305 self.displacements.from_xml(disp_nodes[0], file_version=file_version)
306 307
308 - def get_model(self, model):
309 """Return or create the model. 310 311 @param model: The model number. 312 @type model: int or None 313 @return: The ModelContainer corresponding to the model number or that newly created. 314 @rtype: ModelContainer instance 315 """ 316 317 # Check if the target is a single model. 318 if model == None and self.num_models() > 1: 319 raise RelaxError("The target model cannot be determined as there are %s models already present." % self.num_modes()) 320 321 # No model specified. 322 if model == None: 323 # Create the first model, if necessary. 324 if self.num_models(): 325 self.structural_data.add_item() 326 327 # Alias the first model. 328 model_cont = self.structural_data[0] 329 330 # The model has been specified. 331 else: 332 # Get the preexisting model. 333 found = False 334 for model_cont in self.structural_data: 335 if model_cont.num == model: 336 found = True 337 break 338 339 # Add the model if it doesn't exist. 340 if not found: 341 self.structural_data.add_item(model) 342 model_cont = self.structural_data[-1]
343 344
345 - def get_molecule(self, molecule, model=None):
346 """Return the molecule. 347 348 @param molecule: The molecule name. 349 @type molecule: int or None 350 @keyword model: The model number. 351 @type model: int or None 352 @return: The MolContainer corresponding to the molecule name and model number. 353 @rtype: MolContainer instance or None 354 """ 355 356 # Raise the error. 357 raise RelaxImplementError
358 359
360 - def load_pdb(self, file_path, read_mol=None, set_mol_name=None, read_model=None, set_model_num=None, alt_loc=None, verbosity=False, merge=False):
361 """Prototype method stub for loading structures from a PDB file. 362 363 This inherited prototype method is a stub which, if the functionality is desired, should be overwritten by the derived class. 364 365 366 @param file_path: The full path of the PDB file. 367 @type file_path: str 368 @keyword read_mol: The molecule(s) to read from the file, independent of model. The molecules are determined differently by the different parsers, but are numbered consecutively from 1. If set to None, then all molecules will be loaded. 369 @type read_mol: None, int, or list of int 370 @keyword set_mol_name: Set the names of the molecules which are loaded. If set to None, then the molecules will be automatically labelled based on the file name or other information. 371 @type set_mol_name: None, str, or list of str 372 @keyword read_model: The PDB model to extract from the file. If set to None, then all models will be loaded. 373 @type read_model: None, int, or list of int 374 @keyword set_model_num: Set the model number of the loaded molecule. If set to None, then the PDB model numbers will be preserved, if they exist. 375 @type set_model_num: None, int, or list of int 376 @keyword alt_loc: The PDB ATOM record 'Alternate location indicator' field value to select which coordinates to use. 377 @type alt_loc: str or None 378 @keyword verbosity: A flag which if True will cause messages to be printed. 379 @type verbosity: bool 380 @keyword merge: A flag which if set to True will try to merge the PDB structure into the currently loaded structures. 381 @type merge: bool 382 @return: The status of the loading of the PDB file. 383 @rtype: bool 384 """ 385 386 # Raise the error. 387 raise RelaxImplementError
388 389
390 - def load_xyz(self, file_path, read_mol=None, set_mol_name=None, read_model=None, set_model_num=None, verbosity=False):
391 """Method for loading structures from a XYZ file. 392 393 @param file_path: The full path of the XYZ file. 394 @type file_path: str 395 @keyword read_mol: The molecule(s) to read from the file, independent of model. The 396 molecules are determined differently by the different parsers, but 397 are numbered consecutively from 1. If set to None, then all 398 molecules will be loaded. 399 @type read_mol: None, int, or list of int 400 @keyword set_mol_name: Set the names of the molecules which are loaded. If set to None, 401 then the molecules will be automatically labelled based on the file 402 name or other information. 403 @type set_mol_name: None, str, or list of str 404 @keyword read_model: The XYZ model to extract from the file. If set to None, then all 405 models will be loaded. 406 @type read_model: None, int, or list of int 407 @keyword set_model_num: Set the model number of the loaded molecule. If set to None, then 408 the XYZ model numbers will be preserved, if they exist. 409 @type set_model_num: None, int, or list of int 410 @keyword verbosity: A flag which if True will cause messages to be printed. 411 @type verbosity: bool 412 @return: The status of the loading of the XYZ file. 413 @rtype: bool 414 """ 415 416 # Raise the error. 417 raise RelaxImplementError
418 419
420 - def model_loop(self, model=None):
421 """Generator method for looping over the models in numerical order. 422 423 @keyword model: Limit the loop to a single number. 424 @type model: int 425 @return: The model structural object. 426 @rtype: ModelContainer container 427 """ 428 429 # A single model. 430 if model: 431 for i in range(len(self.structural_data)): 432 if self.structural_data[i].num == model: 433 yield self.structural_data[i] 434 435 # All models. 436 else: 437 # The models. 438 model_nums = [] 439 for i in range(len(self.structural_data)): 440 if self.structural_data[i].num != None: 441 model_nums.append(self.structural_data[i].num) 442 443 # Sort. 444 if model_nums: 445 model_nums.sort() 446 447 # Loop over the models in order. 448 for model_num in model_nums: 449 # Find the model. 450 for i in range(len(self.structural_data)): 451 # Yield the model. 452 if self.structural_data[i].num == model_num: 453 yield self.structural_data[i] 454 455 # No models, so just yield the single container. 456 if not model_nums: 457 yield self.structural_data[0]
458 459
460 - def num_models(self):
461 """Method for returning the number of models. 462 463 @return: The number of models in the structural object. 464 @rtype: int 465 """ 466 467 return len(self.structural_data)
468 469
470 - def num_molecules(self):
471 """Method for returning the number of molecules. 472 473 @return: The number of molecules in the structural object. 474 @rtype: int 475 """ 476 477 # No data. 478 if self.empty(): 479 return 0 480 481 # Validate the structural object. 482 self.validate() 483 484 # Return the number. 485 return len(self.structural_data[0].mol)
486 487
488 - def pack_structs(self, data_matrix, orig_model_num=None, set_model_num=None, orig_mol_num=None, set_mol_name=None, file_name=None, file_path=None, merge=False):
489 """From the given structural data, expand the structural data data structure. 490 491 @param data_matrix: A matrix of structural objects. 492 @type data_matrix: list of lists of structural objects 493 @keyword orig_model_num: The original model numbers (for storage). 494 @type orig_model_num: list of int 495 @keyword set_model_num: The new model numbers (for model renumbering). 496 @type set_model_num: list of int 497 @keyword orig_mol_num: The original molecule numbers (for storage). 498 @type orig_mol_num: list of int 499 @keyword set_mol_name: The new molecule names. 500 @type set_mol_name: list of str 501 @keyword file_name: The name of the file from which the molecular data has been extracted. 502 @type file_name: None or str 503 @keyword file_path: The full path to the file specified by 'file_name'. 504 @type file_path: None or str 505 @keyword merge: A flag which if set to True will try to merge the structure into the currently loaded structures. 506 @type merge: bool 507 """ 508 509 # Test the number of models. 510 if len(orig_model_num) != len(data_matrix): 511 raise RelaxError("Structural data mismatch, %s original models verses %s in the structural data." % (len(orig_model_num), len(data_matrix))) 512 513 # Test the number of molecules. 514 if len(orig_mol_num) != len(data_matrix[0]): 515 raise RelaxError("Structural data mismatch, %s original molecules verses %s in the structural data." % (len(orig_mol_num), len(data_matrix[0]))) 516 517 # Model numbers do not change. 518 if not set_model_num: 519 set_model_num = orig_model_num 520 521 # Test the model mapping. 522 if len(set_model_num) != len(data_matrix): 523 raise RelaxError("Failure of the mapping of new model numbers, %s new model numbers verses %s models in the structural data." % (len(set_model_num), len(data_matrix))) 524 525 # Test the molecule mapping. 526 if len(set_mol_name) != len(data_matrix[0]): 527 raise RelaxError("Failure of the mapping of new molecule names, %s new molecule names verses %s molecules in the structural data." % (len(set_mol_name), len(data_matrix[0]))) 528 529 # Test that the target models and structures are absent, and get the already existing model numbers. 530 current_models = [] 531 for i in range(len(self.structural_data)): 532 # Create a list of current models. 533 current_models.append(self.structural_data[i].num) 534 535 # Loop over the structures. 536 for j in range(len(self.structural_data[i].mol)): 537 if not merge and self.structural_data[i].num in set_model_num and self.structural_data[i].mol[j].mol_name in set_mol_name: 538 raise RelaxError("The molecule '%s' of model %s already exists." % (self.structural_data[i].mol[j].mol_name, self.structural_data[i].num)) 539 540 # Loop over the models. 541 for i in range(len(set_model_num)): 542 # The model doesn't currently exist. 543 if set_model_num[i] not in current_models: 544 # Create the model. 545 self.structural_data.add_item(set_model_num[i]) 546 547 # Add the model number to the current_models list. 548 current_models.append(set_model_num[i]) 549 550 # Get the model. 551 model = self.structural_data[-1] 552 553 # Otherwise get the pre-existing model. 554 else: 555 model = self.structural_data[current_models.index(set_model_num[i])] 556 557 # Loop over the molecules. 558 for j in range(len(set_mol_name)): 559 # Print out. 560 if merge: 561 print("Merging with model %s of molecule '%s' (from the original molecule number %s of model %s)" % (set_model_num[i], set_mol_name[j], orig_mol_num[j], orig_model_num[i])) 562 else: 563 print("Adding molecule '%s' to model %s (from the original molecule number %s of model %s)" % (set_mol_name[j], set_model_num[i], orig_mol_num[j], orig_model_num[i])) 564 565 # The index of the new molecule to add or merge. 566 index = len(model.mol) 567 if merge: 568 index -= 1 569 570 # Consistency check. 571 if model.num != self.structural_data[0].num and self.structural_data[0].mol[index].mol_name != set_mol_name[j]: 572 raise RelaxError("The new molecule name of '%s' in model %s does not match the corresponding molecule's name of '%s' in model %s." % (set_mol_name[j], set_model_num[i], self.structural_data[0].mol[index].mol_name, self.structural_data[0].num)) 573 574 # Pack the structures. 575 if merge: 576 mol = model.mol.merge_item(mol_name=set_mol_name[j], mol_cont=data_matrix[i][j]) 577 else: 578 mol = model.mol.add_item(mol_name=set_mol_name[j], mol_cont=data_matrix[i][j]) 579 580 # Set the molecule name and store the structure file info. 581 mol.mol_name = set_mol_name[j] 582 mol.file_name = file_name 583 mol.file_path = file_path 584 mol.file_mol_num = orig_mol_num[j] 585 mol.file_model = orig_model_num[i]
586 587
588 - def rotate(self, R=None, origin=None, model=None, atom_id=None):
589 """Method stub for rotating a structure. 590 591 @keyword R: The forwards rotation matrix. 592 @type R: numpy 3D, rank-2 array 593 @keyword origin: The origin of the rotation. 594 @type origin: numpy 3D, rank-1 array 595 @keyword model: The model to rotate. If None, all models will be rotated. 596 @type model: int 597 @keyword atom_id: The molecule, residue, and atom identifier string. Only atoms matching this selection will be used. 598 @type atom_id: str or None 599 """ 600 601 # Raise the error. 602 raise RelaxImplementError
603 604
605 - def target_mol_name(self, set=None, target=None, index=None, mol_num=None, file=None):
606 """Add the new molecule name to the target data structure. 607 608 @keyword set: The list of new molecule names. If not supplied, the names will be 609 generated from the file name. 610 @type set: None or list of str 611 @keyword target: The target molecule name data structure to which the new name will be 612 appended. 613 @type target: list 614 @keyword index: The molecule index, matching the set argument. 615 @type index: int 616 @keyword mol_num: The molecule number. 617 @type mol_num: int 618 @keyword file: The name of the file, excluding all directories. 619 @type file: str 620 """ 621 622 # Set the target molecule name. 623 if set: 624 target.append(set[index]) 625 else: 626 # Set the name to the file name plus the structure number. 627 target.append(file_root(file) + '_mol' + repr(mol_num))
628 629
630 - def translate(self, T=None, model=None, atom_id=None):
631 """Method stub for displacing the structural information by the given translation vector. 632 633 @keyword T: The translation vector. 634 @type T: numpy 3D, rank-1 array 635 @keyword model: The model to rotate. If None, all models will be rotated. 636 @type model: int 637 @keyword atom_id: The molecule, residue, and atom identifier string. Only atoms matching this selection will be used. 638 @type atom_id: str or None 639 """ 640 641 # Raise the error. 642 raise RelaxImplementError
643 644
645 - def to_xml(self, doc, element):
646 """Prototype method for converting the structural object to an XML representation. 647 648 @param doc: The XML document object. 649 @type doc: xml.dom.minidom.Document instance 650 @param element: The element to add the alignment tensors XML element to. 651 @type element: XML element object 652 """ 653 654 # Create the structural element and add it to the higher level element. 655 str_element = doc.createElement('structure') 656 element.appendChild(str_element) 657 658 # Set the structural attributes. 659 str_element.setAttribute('desc', 'Structural information') 660 str_element.setAttribute('id', self.id) 661 662 # No contents to store, so pack up the structural containers. 663 if not self.structural_data.is_empty(): 664 self.structural_data.to_xml(doc, str_element) 665 666 # The structural metadata. 667 metadata = ['helices', 'sheets'] 668 for name in metadata: 669 # The metadata does not exist. 670 if not hasattr(self, name): 671 continue 672 673 # Get the object. 674 obj = getattr(self, name) 675 676 # Create a new element for this object, and add it to the main element. 677 sub_elem = doc.createElement(name) 678 str_element.appendChild(sub_elem) 679 680 # Add the value to the sub element. 681 object_to_xml(doc, sub_elem, value=obj) 682 683 # The displacement structure. 684 if hasattr(self, 'displacements'): 685 # Create an XML element. 686 disp_element = doc.createElement('displacements') 687 str_element.appendChild(disp_element) 688 689 # Set the attributes. 690 disp_element.setAttribute('desc', 'The rotational and translational displacements between models') 691 692 # Add the displacement data. 693 self.displacements.to_xml(doc, disp_element)
694 695
696 - def validate_models(self):
697 """Check that the models are consistent with each other. 698 699 This checks that the primary structure is identical between the models. 700 """ 701 702 # Raise the error. 703 raise RelaxImplementError
704 705
706 - def write_pdb(self, file, model_num=None):
707 """Prototype method stub for the creation of a PDB file from the structural data. 708 709 The PDB records 710 =============== 711 712 The following information about the PDB records has been taken from the "Protein Data Bank 713 Contents Guide: Atomic Coordinate Entry Format Description" version 3.1, February 11, 2008. 714 715 HET record 716 ---------- 717 718 The HET record describes non-standard residues. The format of the record is:: 719 __________________________________________________________________________________________ 720 | | | | | 721 | Columns | Data type | Field | Definition | 722 |_________|______________|______________|________________________________________________| 723 | | | | | 724 | 1 - 6 | Record name | "HET " | | 725 | 8 - 10 | LString(3) | hetID | Het identifier, right-justified. | 726 | 13 | Character | ChainID | Chain identifier. | 727 | 14 - 17 | Integer | seqNum | Sequence number. | 728 | 18 | AChar | iCode | Insertion code. | 729 | 21 - 25 | Integer | numHetAtoms | Number of HETATM records for the group present | 730 | | | | in the entry. | 731 | 31 - 70 | String | text | Text describing Het group. | 732 |_________|______________|______________|________________________________________________| 733 734 735 HETNAM record 736 ------------- 737 738 The HETNAM associates a chemical name with the hetID from the HET record. The format is of 739 the record is:: 740 __________________________________________________________________________________________ 741 | | | | | 742 | Columns | Data type | Field | Definition | 743 |_________|______________|______________|________________________________________________| 744 | | | | | 745 | 1 - 6 | Record name | "HETNAM" | | 746 | 9 - 10 | Continuation | continuation | Allows concatenation of multiple records. | 747 | 12 - 14 | LString(3) | hetID | Het identifier, right-justified. | 748 | 16 - 70 | String | text | Chemical name. | 749 |_________|______________|______________|________________________________________________| 750 751 752 FORMUL record 753 ------------- 754 755 The chemical formula for non-standard groups. The format of the record is:: 756 __________________________________________________________________________________________ 757 | | | | | 758 | Columns | Data type | Field | Definition | 759 |_________|______________|______________|________________________________________________| 760 | | | | | 761 | 1 - 6 | Record name | "FORMUL" | | 762 | 9 - 10 | Integer | compNum | Component number. | 763 | 13 - 15 | LString(3) | hetID | Het identifier. | 764 | 17 - 18 | Integer | continuation | Continuation number. | 765 | 19 | Character | asterisk | "*" for water. | 766 | 20 - 70 | String | text | Chemical formula. | 767 |_________|______________|______________|________________________________________________| 768 769 770 MODEL record 771 ------------ 772 773 The model number, for multiple structures. The format of the record is:: 774 __________________________________________________________________________________________ 775 | | | | | 776 | Columns | Data type | Field | Definition | 777 |_________|______________|______________|________________________________________________| 778 | | | | | 779 | 1 - 6 | Record name | "MODEL " | | 780 | 11 - 14 | Integer | serial | Model serial number. | 781 |_________|______________|______________|________________________________________________| 782 783 784 ATOM record 785 ----------- 786 787 The ATOM record contains the atomic coordinates for atoms belonging to standard residues. 788 The format of the record is:: 789 __________________________________________________________________________________________ 790 | | | | | 791 | Columns | Data type | Field | Definition | 792 |_________|______________|______________|________________________________________________| 793 | | | | | 794 | 1 - 6 | Record name | "ATOM" | | 795 | 7 - 11 | Integer | serial | Atom serial number. | 796 | 13 - 16 | Atom | name | Atom name. | 797 | 17 | Character | altLoc | Alternate location indicator. | 798 | 18 - 20 | Residue name | resName | Residue name. | 799 | 22 | Character | chainID | Chain identifier. | 800 | 23 - 26 | Integer | resSeq | Residue sequence number. | 801 | 27 | AChar | iCode | Code for insertion of residues. | 802 | 31 - 38 | Real(8.3) | x | Orthogonal coordinates for X in Angstroms. | 803 | 39 - 46 | Real(8.3) | y | Orthogonal coordinates for Y in Angstroms. | 804 | 47 - 54 | Real(8.3) | z | Orthogonal coordinates for Z in Angstroms. | 805 | 55 - 60 | Real(6.2) | occupancy | Occupancy. | 806 | 61 - 66 | Real(6.2) | tempFactor | Temperature factor. | 807 | 73 - 76 | LString(4) | segID | Segment identifier, left-justified. | 808 | 77 - 78 | LString(2) | element | Element symbol, right-justified. | 809 | 79 - 80 | LString(2) | charge | Charge on the atom. | 810 |_________|______________|______________|________________________________________________| 811 812 813 HETATM record 814 ------------- 815 816 The HETATM record contains the atomic coordinates for atoms belonging to non-standard 817 groups. The format of the record is:: 818 __________________________________________________________________________________________ 819 | | | | | 820 | Columns | Data type | Field | Definition | 821 |_________|______________|______________|________________________________________________| 822 | | | | | 823 | 1 - 6 | Record name | "HETATM" | | 824 | 7 - 11 | Integer | serial | Atom serial number. | 825 | 13 - 16 | Atom | name | Atom name. | 826 | 17 | Character | altLoc | Alternate location indicator. | 827 | 18 - 20 | Residue name | resName | Residue name. | 828 | 22 | Character | chainID | Chain identifier. | 829 | 23 - 26 | Integer | resSeq | Residue sequence number. | 830 | 27 | AChar | iCode | Code for insertion of residues. | 831 | 31 - 38 | Real(8.3) | x | Orthogonal coordinates for X. | 832 | 39 - 46 | Real(8.3) | y | Orthogonal coordinates for Y. | 833 | 47 - 54 | Real(8.3) | z | Orthogonal coordinates for Z. | 834 | 55 - 60 | Real(6.2) | occupancy | Occupancy. | 835 | 61 - 66 | Real(6.2) | tempFactor | Temperature factor. | 836 | 73 - 76 | LString(4) | segID | Segment identifier; left-justified. | 837 | 77 - 78 | LString(2) | element | Element symbol; right-justified. | 838 | 79 - 80 | LString(2) | charge | Charge on the atom. | 839 |_________|______________|______________|________________________________________________| 840 841 842 TER record 843 ---------- 844 845 The end of the ATOM and HETATM records for a chain. According to the draft atomic 846 coordinate entry format description: 847 848 I{"The TER record has the same residue name, chain identifier, sequence number and insertion 849 code as the terminal residue. The serial number of the TER record is one number greater than 850 the serial number of the ATOM/HETATM preceding the TER."} 851 852 The format of the record is:: 853 __________________________________________________________________________________________ 854 | | | | | 855 | Columns | Data type | Field | Definition | 856 |_________|______________|______________|________________________________________________| 857 | | | | | 858 | 1 - 6 | Record name | "TER " | | 859 | 7 - 11 | Integer | serial | Serial number. | 860 | 18 - 20 | Residue name | resName | Residue name. | 861 | 22 | Character | chainID | Chain identifier. | 862 | 23 - 26 | Integer | resSeq | Residue sequence number. | 863 | 27 | AChar | iCode | Insertion code. | 864 |_________|______________|______________|________________________________________________| 865 866 867 CONECT record 868 ------------- 869 870 The connectivity between atoms. This is required for all HET groups and for non-standard 871 bonds. The format of the record is:: 872 __________________________________________________________________________________________ 873 | | | | | 874 | Columns | Data type | Field | Definition | 875 |_________|______________|______________|________________________________________________| 876 | | | | | 877 | 1 - 6 | Record name | "CONECT" | | 878 | 7 - 11 | Integer | serial | Atom serial number | 879 | 12 - 16 | Integer | serial | Serial number of bonded atom | 880 | 17 - 21 | Integer | serial | Serial number of bonded atom | 881 | 22 - 26 | Integer | serial | Serial number of bonded atom | 882 | 27 - 31 | Integer | serial | Serial number of bonded atom | 883 | 32 - 36 | Integer | serial | Serial number of hydrogen bonded atom | 884 | 37 - 41 | Integer | serial | Serial number of hydrogen bonded atom | 885 | 42 - 46 | Integer | serial | Serial number of salt bridged atom | 886 | 47 - 51 | Integer | serial | Serial number of hydrogen bonded atom | 887 | 52 - 56 | Integer | serial | Serial number of hydrogen bonded atom | 888 | 57 - 61 | Integer | serial | Serial number of salt bridged atom | 889 |_________|______________|______________|________________________________________________| 890 891 892 ENDMDL record 893 ------------- 894 895 The end of model record, for multiple structures. The format of the record is:: 896 __________________________________________________________________________________________ 897 | | | | | 898 | Columns | Data type | Field | Definition | 899 |_________|______________|______________|________________________________________________| 900 | | | | | 901 | 1 - 6 | Record name | "ENDMDL" | | 902 |_________|______________|______________|________________________________________________| 903 904 905 906 MASTER record 907 ------------- 908 909 The control record for bookkeeping. The format of the record is:: 910 __________________________________________________________________________________________ 911 | | | | | 912 | Columns | Data type | Field | Definition | 913 |_________|______________|______________|________________________________________________| 914 | | | | | 915 | 1 - 6 | Record name | "MASTER" | | 916 | 11 - 15 | Integer | numRemark | Number of REMARK records | 917 | 16 - 20 | Integer | "0" | | 918 | 21 - 25 | Integer | numHet | Number of HET records | 919 | 26 - 30 | Integer | numHelix | Number of HELIX records | 920 | 31 - 35 | Integer | numSheet | Number of SHEET records | 921 | 36 - 40 | Integer | numTurn | Number of TURN records | 922 | 41 - 45 | Integer | numSite | Number of SITE records | 923 | 46 - 50 | Integer | numXform | Number of coordinate transformation records | 924 | | | | (ORIGX+SCALE+MTRIX) | 925 | 51 - 55 | Integer | numCoord | Number of atomic coordinate records | 926 | | | | (ATOM+HETATM) | 927 | 56 - 60 | Integer | numTer | Number of TER records | 928 | 61 - 65 | Integer | numConect | Number of CONECT records | 929 | 66 - 70 | Integer | numSeq | Number of SEQRES records | 930 |_________|______________|______________|________________________________________________| 931 932 933 END record 934 ---------- 935 936 The end of the PDB file. The format of the record is:: 937 __________________________________________________________________________________________ 938 | | | | | 939 | Columns | Data type | Field | Definition | 940 |_________|______________|______________|________________________________________________| 941 | | | | | 942 | 1 - 6 | Record name | "END " | | 943 |_________|______________|______________|________________________________________________| 944 945 946 @param file: The PDB file object. This object must be writable. 947 @type file: file object 948 @keyword model_num: The model to place into the PDB file. If not supplied, then all 949 models will be placed into the file. 950 @type model_num: None or int 951 """ 952 953 # Raise the error. 954 raise RelaxImplementError
955 956
957 - def validate(self):
958 """Check the integrity of the structural data. 959 960 The number of molecules must be the same in all models. 961 """ 962 963 # Reference number of molecules. 964 num_mols = len(self.structural_data[0].mol) 965 966 # Loop over all other models. 967 for i in range(1, len(self.structural_data)): 968 # Model alias. 969 model_i = self.structural_data[i] 970 971 # Size check. 972 if num_mols != len(model_i.mol): 973 raise RelaxError("The structural object is not valid - the number of molecules is not the same for all models.") 974 975 # Molecule name check. 976 for j in range(len(model_i.mol)): 977 if model_i.mol[j].mol_name != self.structural_data[0].mol[j].mol_name: 978 raise RelaxError("The molecule name '%s' of model %s does not match the corresponding molecule '%s' of model %s." % (model_i.mol[j].mol_name, model_i.num, self.structural_data[0].mol[j].mol_name, self.structural_data[0].num))
979 980 981
982 -class Displacements:
983 """A special object for representing rotational and translational displacements between models.""" 984
985 - def __init__(self):
986 """Initialise the storage objects.""" 987 988 # The displacement structures. 989 self._translation_vector = {} 990 self._translation_distance = {} 991 self._rotation_matrix = {} 992 self._rotation_axis = {} 993 self._rotation_angle = {}
994 995
996 - def _calculate(self, model_from=None, model_to=None, coord_from=None, coord_to=None, centroid=None):
997 """Calculate the rotational and translational displacements using the given coordinate sets. 998 999 This uses the U{Kabsch algorithm<http://en.wikipedia.org/wiki/Kabsch_algorithm>}. 1000 1001 1002 @keyword model_from: The model number of the starting structure. 1003 @type model_from: int 1004 @keyword model_to: The model number of the ending structure. 1005 @type model_to: int 1006 @keyword coord_from: The list of atomic coordinates for the starting structure. 1007 @type coord_from: numpy rank-2, Nx3 array 1008 @keyword coord_to: The list of atomic coordinates for the ending structure. 1009 @type coord_to: numpy rank-2, Nx3 array 1010 @keyword centroid: An alternative position of the centroid, used for studying pivoted systems. 1011 @type centroid: list of float or numpy rank-1, 3D array 1012 """ 1013 1014 # Initialise structures if necessary. 1015 if not model_from in self._translation_vector: 1016 self._translation_vector[model_from] = {} 1017 if not model_from in self._translation_distance: 1018 self._translation_distance[model_from] = {} 1019 if not model_from in self._rotation_matrix: 1020 self._rotation_matrix[model_from] = {} 1021 if not model_from in self._rotation_axis: 1022 self._rotation_axis[model_from] = {} 1023 if not model_from in self._rotation_angle: 1024 self._rotation_angle[model_from] = {} 1025 1026 # The Kabsch algorithm. 1027 trans_vect, trans_dist, R, axis, angle, pivot = kabsch(name_from='model %s'%model_from, name_to='model %s'%model_to, coord_from=coord_from, coord_to=coord_to, centroid=centroid) 1028 1029 # Store the data. 1030 self._translation_vector[model_from][model_to] = trans_vect 1031 self._translation_distance[model_from][model_to] = trans_dist 1032 self._rotation_matrix[model_from][model_to] = R 1033 self._rotation_axis[model_from][model_to] = axis 1034 self._rotation_angle[model_from][model_to] = angle
1035 1036
1037 - def from_xml(self, str_node, dir=None, id=None, file_version=1):
1038 """Recreate the structural object from the XML structural object node. 1039 1040 @param str_node: The structural object XML node. 1041 @type str_node: xml.dom.minicompat.Element instance 1042 @keyword dir: The name of the directory containing the results file. 1043 @type dir: str 1044 @keyword id: The specific structural object ID string. This can be 'scientific', 'internal', etc. 1045 @type id: str 1046 @keyword file_version: The relax XML version of the XML file. 1047 @type file_version: int 1048 """ 1049 1050 # Get the pairs of displacements. 1051 pair_nodes = str_node.getElementsByTagName('pair') 1052 1053 # Loop over the pairs. 1054 for pair_node in pair_nodes: 1055 # Get the two models. 1056 model_from = int(pair_node.getAttribute('model_from')) 1057 model_to = int(pair_node.getAttribute('model_to')) 1058 1059 # Initialise structures if necessary. 1060 if not model_from in self._translation_vector: 1061 self._translation_vector[model_from] = {} 1062 if not model_from in self._translation_distance: 1063 self._translation_distance[model_from] = {} 1064 if not model_from in self._rotation_matrix: 1065 self._rotation_matrix[model_from] = {} 1066 if not model_from in self._rotation_axis: 1067 self._rotation_axis[model_from] = {} 1068 if not model_from in self._rotation_angle: 1069 self._rotation_angle[model_from] = {} 1070 1071 # A temporary container to place the Python objects into. 1072 cont = ModelContainer() 1073 1074 # Recreate the Python objects. 1075 xml_to_object(pair_node, cont, file_version=file_version) 1076 1077 # Repackage the data. 1078 for name in ['translation_vector', 'translation_distance', 'rotation_matrix', 'rotation_axis', 'rotation_angle']: 1079 # The objects. 1080 obj = getattr(self, '_'+name) 1081 obj_temp = getattr(cont, name) 1082 1083 # Store. 1084 obj[model_from][model_to] = obj_temp
1085 1086
1087 - def to_xml(self, doc, element):
1088 """Create XML elements for each model. 1089 1090 @param doc: The XML document object. 1091 @type doc: xml.dom.minidom.Document instance 1092 @param element: The element to add the displacement XML elements to. 1093 @type element: XML element object 1094 """ 1095 1096 # Loop over the starting models. 1097 start_models = sorted(self._translation_vector.keys()) 1098 for model_from in start_models: 1099 # Loop over the ending models. 1100 end_models = sorted(self._translation_vector[model_from].keys()) 1101 for model_to in end_models: 1102 # Create an XML element for each pair. 1103 pair_element = doc.createElement('pair') 1104 element.appendChild(pair_element) 1105 1106 # Set the attributes. 1107 pair_element.setAttribute('desc', 'The displacement from model %s to model %s' % (model_from, model_to)) 1108 pair_element.setAttribute('model_from', str(model_from)) 1109 pair_element.setAttribute('model_to', str(model_to)) 1110 1111 # The objects to store. 1112 obj_names = [ 1113 '_translation_vector', 1114 '_translation_distance', 1115 '_rotation_matrix', 1116 '_rotation_axis', 1117 '_rotation_angle' 1118 ] 1119 1120 # Store the objects. 1121 for i in range(len(obj_names)): 1122 # Create a new element for this object, and add it to the main element. 1123 sub_elem = doc.createElement(obj_names[i][1:]) 1124 pair_element.appendChild(sub_elem) 1125 1126 # Get the sub-object. 1127 subobj = getattr(self, obj_names[i])[model_from][model_to] 1128 1129 # Add the value to the sub element. 1130 object_to_xml(doc, sub_elem, value=subobj)
1131 1132 1133
1134 -class ModelList(list):
1135 """List type data container for the different structural models. 1136 1137 Here different models are defined as the same molecule but with different conformations. 1138 """ 1139
1140 - def __repr__(self):
1141 """The string representation of the object. 1142 1143 Rather than using the standard Python conventions (either the string representation of the 1144 value or the "<...desc...>" notation), a rich-formatted description of the object is given. 1145 """ 1146 1147 text = "Models.\n\n" 1148 text = text + "%-8s%-8s" % ("Index", "Model number") + "\n" 1149 for i in range(len(self)): 1150 text = text + "%-8i%-8s" % (i, self[i].num) + "\n" 1151 return text
1152 1153
1154 - def add_item(self, model_num=None):
1155 """Append an empty ModelContainer to the ModelList. 1156 1157 @keyword model_num: The model number. 1158 @type model_num: int 1159 """ 1160 1161 # If no model data exists, replace the empty first model with this model (just a renumbering). 1162 if len(self) and self.is_empty(): 1163 self[0].num = model_num 1164 1165 # Otherwise append an empty ModelContainer. 1166 else: 1167 # Test if the model number already exists. 1168 for i in range(len(self)): 1169 if self[i].num == model_num: 1170 raise RelaxError("The model '" + repr(model_num) + "' already exists.") 1171 1172 # Append an empty ModelContainer. 1173 self.append(ModelContainer(model_num))
1174 1175
1176 - def is_empty(self):
1177 """Method for testing if this ModelList object is empty. 1178 1179 @return: True if this list only has one ModelContainer and the model name has not 1180 been set, False otherwise. 1181 @rtype: bool 1182 """ 1183 1184 # No ModelContainers. 1185 if len(self) == 0: 1186 return True 1187 1188 # There is only one ModelContainer and it is empty. 1189 if len(self) == 1 and self[0].is_empty(): 1190 return True 1191 1192 # Otherwise. 1193 return False
1194 1195
1196 - def from_xml(self, model_nodes, id=None, file_version=1):
1197 """Recreate a model list data structure from the XML model nodes. 1198 1199 @param model_nodes: The model XML nodes. 1200 @type model_nodes: xml.dom.minicompat.NodeList instance 1201 @keyword id: The specific structural object ID string. This can be 'scientific', 'internal', etc. 1202 @type id: str 1203 @keyword file_version: The relax XML version of the XML file. 1204 @type file_version: int 1205 """ 1206 1207 # Test if empty. 1208 if not self.is_empty(): 1209 raise RelaxFromXMLNotEmptyError(self.__class__.__name__) 1210 1211 # Loop over the models. 1212 for model_node in model_nodes: 1213 # Get the model details and add the model to the ModelList structure. 1214 num = eval(model_node.getAttribute('num')) 1215 if num == 'None': 1216 num = None 1217 self.add_item(model_num=num) 1218 1219 # Get the molecule nodes. 1220 mol_nodes = model_node.getElementsByTagName('mol_cont') 1221 1222 # Recreate the molecule data structures for the current model. 1223 self[-1].mol.from_xml(mol_nodes, id=id, file_version=file_version)
1224 1225
1226 - def to_xml(self, doc, element):
1227 """Create XML elements for each model. 1228 1229 @param doc: The XML document object. 1230 @type doc: xml.dom.minidom.Document instance 1231 @param element: The element to add the model XML elements to. 1232 @type element: XML element object 1233 """ 1234 1235 # Loop over the models. 1236 for i in range(len(self)): 1237 # Create an XML element for this model and add it to the higher level element. 1238 model_element = doc.createElement('model') 1239 element.appendChild(model_element) 1240 1241 # Set the model attributes. 1242 model_element.setAttribute('desc', 'Model container') 1243 model_element.setAttribute('num', str(self[i].num)) 1244 1245 # Add all simple python objects within the ModelContainer to the XML element. 1246 fill_object_contents(doc, model_element, object=self[i], blacklist=['num', 'mol'] + list(self[i].__class__.__dict__.keys())) 1247 1248 # Add the molecule data. 1249 self[i].mol.to_xml(doc, model_element)
1250 1251 1252
1253 -class ModelContainer(object):
1254 """Class containing all the model specific data.""" 1255
1256 - def __init__(self, model_num=None):
1257 """Set up the default objects of the model data container.""" 1258 1259 # The model number. 1260 self.num = model_num 1261 1262 # The empty molecule list. 1263 self.mol = MolList()
1264 1265
1266 - def __repr__(self):
1267 """The string representation of the object. 1268 1269 Rather than using the standard Python conventions (either the string representation of the 1270 value or the "<...desc...>" notation), a rich-formatted description of the object is given. 1271 """ 1272 1273 # Intro. 1274 text = "Class containing the data for model %s.\n" % self.num 1275 1276 # Objects. 1277 text = text + "\n" 1278 text = text + "Objects:\n" 1279 for name in dir(self): 1280 # Molecule list. 1281 if name == 'mol': 1282 text = text + " mol: The list of %s molecules within the model.\n" % len(self.mol) 1283 continue 1284 1285 # Skip the ModelContainer methods. 1286 if name == 'is_empty': 1287 continue 1288 1289 # Skip special objects. 1290 if match("^__", name): 1291 continue 1292 1293 # Add the object's attribute to the text string. 1294 text = text + " " + name + ": " + repr(getattr(self, name)) + "\n" 1295 1296 return text
1297 1298
1299 - def is_empty(self):
1300 """Method for testing if this ModelContainer object is empty. 1301 1302 @return: True if this container is empty and the model number has not been set, False 1303 otherwise. 1304 @rtype: bool 1305 """ 1306 1307 # The model num has been set. 1308 if self.num != None: 1309 return False 1310 1311 # An object has been added to the container. 1312 for name in dir(self): 1313 # Skip the objects initialised in __init__(). 1314 if name == 'num' or name == 'mol': 1315 continue 1316 1317 # Skip the ModelContainer methods. 1318 if name == 'is_empty': 1319 continue 1320 1321 # Skip special objects. 1322 if match("^__", name): 1323 continue 1324 1325 # An object has been added. 1326 return False 1327 1328 # The molecule list is not empty. 1329 if not self.mol.is_empty(): 1330 return False 1331 1332 # The ModelContainer is unmodified. 1333 return True
1334 1335 1336
1337 -class MolList(list):
1338 """List type data container for holding the different molecules of one model.""" 1339
1340 - def __repr__(self):
1341 """The string representation of the object. 1342 1343 Rather than using the standard Python conventions (either the string representation of the 1344 value or the "<...desc...>" notation), a rich-formatted description of the object is given. 1345 """ 1346 1347 text = "Molecules.\n\n" 1348 text = text + "%-8s%-8s" % ("Index", "Name") + "\n" 1349 for i in range(len(self)): 1350 text = text + "%-8i%-8s" % (i, self[i].mol_name) + "\n" 1351 return text
1352 1353
1354 - def add_item(self, mol_name=None, mol_cont=None):
1355 """Append the given MolContainer instance to the MolList. 1356 1357 @keyword mol_name: The molecule number. 1358 @type mol_name: int 1359 @keyword mol_cont: The data structure for the molecule. 1360 @type mol_cont: MolContainer instance 1361 @return: The new molecule container. 1362 @rtype: MolContainer instance 1363 """ 1364 1365 # If no molecule data exists, replace the empty first molecule with this molecule (just a renaming). 1366 if len(self) and self.is_empty(): 1367 self[0].mol_name = mol_name 1368 1369 # Otherwise append an empty MolContainer. 1370 else: 1371 # Test if the molecule already exists. 1372 for i in range(len(self)): 1373 if self[i].mol_name == mol_name: 1374 raise RelaxError("The molecule '%s' already exists." % mol_name) 1375 1376 # Append an empty MolContainer. 1377 self.append(mol_cont) 1378 1379 # Set the name. 1380 self[-1].mol_name = mol_name 1381 1382 # Return the container. 1383 return self[-1]
1384 1385
1386 - def is_empty(self):
1387 """Method for testing if this MolList object is empty. 1388 1389 @return: True if this list only has one MolContainer and the molecule name has not 1390 been set, False otherwise. 1391 @rtype: bool 1392 """ 1393 1394 # No MolContainers. 1395 if len(self) == 0: 1396 return True 1397 1398 # There is only one MolContainer and it is empty. 1399 if len(self) == 1 and hasattr(self[0], 'is_empty') and self[0].is_empty(): 1400 return True 1401 1402 # Otherwise. 1403 return False
1404 1405
1406 - def from_xml(self, mol_nodes, id=None, file_version=1):
1407 """Recreate a molecule list data structure from the XML molecule nodes. 1408 1409 @param mol_nodes: The molecule XML nodes. 1410 @type mol_nodes: xml.dom.minicompat.NodeList instance 1411 @keyword id: The specific structural object ID string. This can be 'scientific', 'internal', etc. 1412 @type id: str 1413 @keyword file_version: The relax XML version of the XML file. 1414 @type file_version: int 1415 """ 1416 1417 # Test if empty. 1418 if not self.is_empty(): 1419 raise RelaxFromXMLNotEmptyError(self.__class__.__name__) 1420 1421 # Loop over the molecules. 1422 for mol_node in mol_nodes: 1423 # Some imports (here to break circular import issues). 1424 if id == 'internal': 1425 from generic_fns.structure.internal import MolContainer 1426 elif id == 'scientific': 1427 from generic_fns.structure.scientific import MolContainer 1428 1429 # Initialise a MolContainer instance. 1430 mol_cont = MolContainer() 1431 1432 # Get the molecule name. 1433 name = mol_node.getAttribute('name') 1434 if name == 'None': 1435 name = None 1436 1437 # Add the molecule to the MolList structure. 1438 self.add_item(mol_name=name, mol_cont=mol_cont) 1439 1440 # Execute the specific MolContainer from_xml() method. 1441 self[-1].from_xml(mol_node, file_version=file_version)
1442 1443
1444 - def merge_item(self, mol_name=None, mol_cont=None):
1445 """Mege the given MolContainer instance into a pre-existing molecule container. 1446 1447 @keyword mol_name: The molecule number. 1448 @type mol_name: int 1449 @keyword mol_cont: The data structure for the molecule. 1450 @type mol_cont: MolContainer instance 1451 @return: The new molecule container. 1452 @rtype: MolContainer instance 1453 """ 1454 1455 # Find the molecule to merge. 1456 index = None 1457 for i in range(len(self)): 1458 if self[i].mol_name == mol_name: 1459 index = i 1460 break 1461 1462 # No molecule found. 1463 if index == None: 1464 raise RelaxError("The molecule '%s' to merge with cannot be found." % mol_name) 1465 1466 # Merge the molecules. 1467 self[index].merge(mol_cont) 1468 1469 # Return the container. 1470 return self[index]
1471 1472
1473 - def to_xml(self, doc, element):
1474 """Create XML elements for each molecule. 1475 1476 @param doc: The XML document object. 1477 @type doc: xml.dom.minidom.Document instance 1478 @param element: The element to add the molecule XML elements to. 1479 @type element: XML element object 1480 """ 1481 1482 # Loop over the molecules. 1483 for i in range(len(self)): 1484 # Add the molecule data. 1485 self[i].to_xml(doc, element)
1486