def setResValues( self, model, values, key='temperature_factor', lastOnly=0 ): """ Add numeric value per residue to all atoms of all Structures or the last Structure in a model. The values will be written to either the B- (temperature_factor) or Q-factor 'occupancy' column in the temporary pdb-file. These values can then be used to display properties in PyMol via commands like 'color_b' and 'color_q'. See also L{setAtomValues}. @param model: model name @type model: str @param values: list of numbers, len( values ) == number of residues @type values: [float] @param key: key for Atom.properties dictionary (default: temperature_factor) @type key: occupancy|temperature_factor @param lastOnly: 0 - add to all in model OR 1 - add only to last Structure (default: 0) @type lastOnly: 1|0 """ if lastOnly: self.dic[ model ][-1].addResProperty( values, key ) else: for m in self.dic[ model ]: try: m.addResProperty( values, key ) except: T.errWriteln( "Warning: error while adding properties.") T.errWriteln( "Key: "+str( key )+" values: "+str( values ) ) T.errWriteln( T.lastError() )
def array_or_list( self, prof, asarray ): """ Convert to array or list depending on asarray option Beware: empty lists will be upgraded to empty Float arrays. :param prof: profile :type prof: list OR array :param asarray: 1.. autodetect type, 0.. force list, 2.. force array :type asarray: 2|1|0 :return: profile :rtype: list OR array :raise ProfileError: """ try: ## autodetect type if asarray == 1: if isinstance( prof, N.ndarray ): return self.__picklesave_array( prof ) if type( prof ) is str: # tolerate strings as profiles return list( prof ) if len(prof) == 0: # don't create arrays from empty lists return list( prof ) p = self.__picklesave_array( N.array( prof ) ) if p.dtype.char not in ['O','c','S', 'U']: ## no char or object arrays! return p return list( prof ) ## force list if asarray == 0: if isinstance( prof, N.ndarray ): return prof.tolist() return list( prof ) ## force array if asarray == 2: if isinstance( prof, N.ndarray ): return self.__picklesave_array( prof ) return self.__picklesave_array( N.array( prof ) ) except TypeError as why: ## Numeric bug: N.array(['','','']) raises TypeError if asarray == 1 or asarray == 0: return list( prof ) raise ProfileError("Cannot create array from given list. %r"\ % T.lastError()) raise ProfileError("%r not allowed as value for asarray" % asarray)
def add(self, str): """ Add String str and line break to file. @param str: string to add to pml file @type str: str """ try: self.fgenerate.write(str + '\n') except (IOError): T.errWriteln( "PymolInput.add(): Error adding string to pymol script file.") T.errWriteln( T.lastError() )
def failed( self ): """ If HEX job fails """ print("FAILED: ", self.host, ' ', t.stripFilename(self.finp)) print("\tJob details:") print("\tCommand: ", self.cmd) print("\tinput: ", self.finp) print("\tHex log: ", self.log) print("\tHex out: ", self.fout) print() print("\t", t.lastError()) self.owner.failedHex( self )
def failed(self): """ If HEX job fails """ print("FAILED: ", self.host, ' ', t.stripFilename(self.finp)) print("\tJob details:") print("\tCommand: ", self.cmd) print("\tinput: ", self.finp) print("\tHex log: ", self.log) print("\tHex out: ", self.fout) print() print("\t", t.lastError()) self.owner.failedHex(self)
def fatal( self, message ): """ Handle a fatal error (likely a bug), stop program execution. :param message: message to be given to user :type message: str :raise FatalError: """ s = '\nFatal Error: '+str(message) s += '\n\t' + T.lastError() + '\n' s += 'TraceBack: \n' + T.lastErrorTrace() + '\n' self.log.add(s) raise FatalError
def error( self, message ): """ Handle a normal error (like non-existing file) that is not necessarily a bug. :param message: message to be given to user :type message: str :raise NormalError: """ s = '\nError: '+str(message) s += '\n\t' + T.lastError() s += '\nTraceBack: \n' + T.lastErrorTrace() + '\n' self.log.add(s) raise NormalError
def remove_multi_occupancies( self ): """ Keep only atoms with alternate A field (well, or no alternate). """ if self.verbose: self.logWrite( self.model.pdbCode + ': Removing multiple occupancies of atoms ...') i = 0 to_be_removed = [] for a in self.model: if a['alternate']: try: str_id = "%i %s %s %i" % (a['serial_number'], a['name'], a['residue_name'], a['residue_number']) if a['alternate'].upper() in ['A', '1']: a['alternate'] = '' else: if float( a['occupancy'] ) < 1.0: to_be_removed += [ i ] if self.verbose: self.logWrite( 'removing %s (%s %s)' % (str_id,a['alternate'], a['occupancy'])) else: if self.verbose: self.logWrite( ('keeping non-A duplicate %s because of 1.0 '+ 'occupancy') % str_id ) except: self.logWrite("Error removing duplicate: "+t.lastError() ) i+=1 try: self.model.remove( to_be_removed ) if self.verbose: self.logWrite('Removed %i atoms' % len( to_be_removed ) ) except: if self.verbose: self.logWrite('No atoms with multiple occupancies to remove' )
def remove_multi_occupancies(self): """ Keep only atoms with alternate A field (well, or no alternate). """ if self.verbose: self.logWrite(self.model.pdbCode + ': Removing multiple occupancies of atoms ...') i = 0 to_be_removed = [] for a in self.model: if a['alternate']: try: str_id = "%i %s %s %i" % (a['serial_number'], a['name'], a['residue_name'], a['residue_number']) if a['alternate'].upper() in ['A', '1']: a['alternate'] = '' else: if float(a['occupancy']) < 1.0: to_be_removed += [i] if self.verbose: self.logWrite( 'removing %s (%s %s)' % (str_id, a['alternate'], a['occupancy'])) else: if self.verbose: self.logWrite(( 'keeping non-A duplicate %s because of 1.0 ' + 'occupancy') % str_id) except: self.logWrite("Error removing duplicate: " + t.lastError()) i += 1 try: self.model.remove(to_be_removed) if self.verbose: self.logWrite('Removed %i atoms' % len(to_be_removed)) except: if self.verbose: self.logWrite('No atoms with multiple occupancies to remove')
def warning( self, message, error=1, trace=0 ): """ Issue a warning. No exception is raised. :param message: message to be given to user :type message: str :param error: report Exception with line (default: 1) :type error: 1||0 :param trace: report full back trace to exception (default: 0) :type trace: 1||0 """ s = '\nWarning (ignored): '+str(message) try: if trace: error = 1 if error: s += '\n\t' + T.lastError() + '\n' if trace: s += '\nTraceBack: \n' + T.lastErrorTrace() + '\n' except: pass self.log.add(s)
def addResProperty( self, values, key='temperature_factor'): """ Does the same thing as L{addProperty} but on the residue level, i.e adds extra value to each residue in Structure. (The same value is added to all atoms of a residue.) These values can then be used to display properties in PyMol via commands like 'color_b' and 'color_q'. @param values: list of numbers, len( values ) == number of residues @type values: [float] @param key: key for Atom.properties dictionary ('occupancy' OR 'temperature_factor') @type key: occupancy|temperature_factor """ try: if self.struct is None: self.struct = PDBModel( self.fname ) self.temporary = 1 self.struct[ key ] = self.struct.res2atomProfile( values ) except: print(T.lastError())
def generateInp(self): """ Prepare the program input (file or string) from a template (if any, file or string). :return: input file name OR (if pipes=1) content of input file OR None :rtype: str :raise TemplateError: if error while creating template file """ try: inp = None if self.template: inp = self.fillTemplate() return self.convertInput(inp) except IOError as why: s = "Error while creating input file from template." s += "\n template file: " + str(self.template) s += "\n why: " + str(why) s += "\n Error:\n " + t.lastError() raise TemplateError(s)
def generateInp(self): """ Prepare the program input (file or string) from a template (if any, file or string). :return: input file name OR (if pipes=1) content of input file OR None :rtype: str :raise TemplateError: if error while creating template file """ try: inp = None if self.template: inp = self.fillTemplate() return self.convertInput( inp ) except IOError as why: s = "Error while creating input file from template." s += "\n template file: " + str( self.template ) s += "\n why: " + str( why ) s += "\n Error:\n " + t.lastError() raise TemplateError(s)
def update(self, model, source, skipRes=None, updateMissing=0, force=0, headPatterns=[]): """ Update empty or missing fields of model from the source. The model will be connected to the source via model.source. Profiles that are derived from the source are labeled 'changed'=0. The same holds for coordinates (xyzChanged=0). However, existing profiles or coordinates or fields remain untouched. :param model: existing model :type model: PDBModel :param source: source PDB file :type source: str :param skipRes: list residue names that should not be parsed :type skipRes: [ str ] :param updateMissing: ignored :type updateMissing: 1|0 :param headPatterns: [(putIntoKey, regex)] extract given REMARKS :type headPatterns: [(str, str)] :raise PDBParserError - if something is wrong with the source file """ try: ## atoms and/or coordinates need to be updated from PDB if force or self.needsUpdate(model): atoms, xyz, info = self.__collectAll(source, skipRes, headPatterns) keys = M.union(list(atoms.keys()), list(self.DEFAULTS.keys())) for k in keys: a = model.atoms.get(k, default=0, update=False) if (a is 0) or (a is None): dflt = self.DEFAULTS.get(k, None) model.atoms.set(k, atoms.get(k, dflt), changed=0) if model.xyz is None: model.xyz = xyz model.xyzChanged = 0 model._resIndex = None model._chainIndex = None model.fileName = model.fileName or source model.pdbCode = model.pdbCode or info.get('pdb_code', None) or \ self.idFromName( model.fileName) ## ## make biounit from the dictionary we have parsed if 'BIOMT' in info: ## biomt = info['BIOMT'] ## model.biounit = BU.BioUnit(model, biomt) ## del info['BIOMT'] model.info.update(info) except: msg = self.__xplorAtomIndicesTest(source) or ' ' raise PDBParserError('Cannot read ' + str(source) + ' as PDB\n'\ '\ERROR: ' + T.lastError() + msg) model.setSource(source)
def array_or_list(self, prof, asarray): """ Convert to array or list depending on asarray option Beware: empty lists will be upgraded to empty Float arrays. :param prof: profile :type prof: list OR array :param asarray: 1.. autodetect type, 0.. force list, 2.. force array :type asarray: 2|1|0 :return: profile :rtype: list OR array :raise ProfileError: """ try: ## autodetect type if asarray == 1: if isinstance(prof, N.ndarray): return self.__picklesave_array(prof) if type(prof) is str: # tolerate strings as profiles return list(prof) if len(prof) == 0: # don't create arrays from empty lists return list(prof) p = self.__picklesave_array(N.array(prof)) if p.dtype.char not in ['O', 'c', 'S', 'U']: ## no char or object arrays! return p return list(prof) ## force list if asarray == 0: if isinstance(prof, N.ndarray): return prof.tolist() return list(prof) ## force array if asarray == 2: if isinstance(prof, N.ndarray): return self.__picklesave_array(prof) return self.__picklesave_array(N.array(prof)) except TypeError as why: ## Numeric bug: N.array(['','','']) raises TypeError if asarray == 1 or asarray == 0: return list(prof) raise ProfileError("Cannot create array from given list. %r"\ % T.lastError()) raise ProfileError("%r not allowed as value for asarray" % asarray)
def __collectAll(self, fname, skipRes=None, headPatterns=[]): """ Parse ATOM/HETATM lines from PDB. Collect coordinates plus dictionaries with the other pdb records of each atom. REMARK, HEADER, etc. lines are ignored. Some changes are made to the dictionary from PDBFile.readline():: - the 'position' entry (with the coordinates) is removed - leading and trailing spaces are removed from 'name' .. - .. but a 'name_original' entry keeps the old name with spaces - a 'type' entry is added. Its value is 'ATOM' or 'HETATM' - a 'after_ter' entry is added. Its value is 1, if atom is preceeded by a 'TER' line, otherwise 0 - empty 'element' entries are filled with the first non-number letter from the atom 'name' :param fname: name of pdb file :type fname: str :param skipRes: list with residue names that should be skipped :type skipRes: list of str :return: tuple of (1) dictionary of profiles and (2) xyz array N x 3 :rtype: ( list, array ) """ xyz = [] aProfs = {} info = {} in_header = True headPatterns = headPatterns or self.RE_REMARKS patterns = [(key, re.compile(ex)) for key, ex in headPatterns] for k in B.PDBModel.PDB_KEYS: aProfs[k] = list() f = IO.PDBFile(fname) skipLine = False try: line, i = ('', ''), 0 while line[0] != 'END' and line[0] != 'ENDMDL': i += 1 if not skipLine: try: line = f.readLine() except ValueError as what: self.log.add('Warning: Error parsing line %i of %s' % (i, T.stripFilename(fname))) self.log.add('\tError: ' + str(what)) continue else: skipLine = False ## header handling if in_header and line[0] == 'HEADER': info.update(self.__parseHeader(line)) if in_header and line[0] == 'REMARK': if line[1].startswith(' 350'): biomtDict, line = self.__parseBiomt(f, line) info.update(biomtDict) # we've hogged a line beyond REMARK 350 records in # __parseBiomt(), now we need to process it here skipLine = True continue else: info.update(self.__parseRemark(line, patterns)) ## preserve position of TER records newChain = line[0] == 'TER' if newChain: line = f.readLine() if (line[0] in ['ATOM', 'HETATM']): if in_header: in_header = False ## switch off HEADER parsing a = line[1] if skipRes and a['residue_name'] in skipRes: continue a['name_original'] = a['name'] a['name'] = a['name'].strip() a['type'] = line[0] if newChain: a['after_ter'] = 1 else: a['after_ter'] = 0 if a['element'] == '': a['element'] = self.__firstLetter(a['name']) xyz.append(a['position']) del a['position'] for k, v in a.items(): aProfs[k].append(v) except: raise PDBParserError("Error parsing file "+fname+": " \ + T.lastError()) try: f.close() except: pass if len(xyz) == 0: raise PDBParserError("Error parsing file " + fname + ": " + "Couldn't find any atoms.") return aProfs, N0.array(xyz, N0.Float32), info
def update( self, model, source, skipRes=None, updateMissing=0, force=0, headPatterns=[]): """ Update empty or missing fields of model from the source. The model will be connected to the source via model.source. Profiles that are derived from the source are labeled 'changed'=0. The same holds for coordinates (xyzChanged=0). However, existing profiles or coordinates or fields remain untouched. :param model: existing model :type model: PDBModel :param source: source PDB file :type source: str :param skipRes: list residue names that should not be parsed :type skipRes: [ str ] :param updateMissing: ignored :type updateMissing: 1|0 :param headPatterns: [(putIntoKey, regex)] extract given REMARKS :type headPatterns: [(str, str)] :raise PDBParserError - if something is wrong with the source file """ try: ## atoms and/or coordinates need to be updated from PDB if force or self.needsUpdate( model ): atoms, xyz, info = self.__collectAll( source, skipRes, headPatterns ) keys = M.union( list(atoms.keys()), list(self.DEFAULTS.keys()) ) for k in keys: a = model.atoms.get( k, default=0, update=False ) if (a is 0) or (a is None): dflt = self.DEFAULTS.get( k, None ) model.atoms.set(k, atoms.get(k, dflt), changed=0 ) if model.xyz is None: model.xyz = xyz model.xyzChanged = 0 model._resIndex =None model._chainIndex=None model.fileName = model.fileName or source model.pdbCode = model.pdbCode or info.get('pdb_code', None) or \ self.idFromName( model.fileName) ## ## make biounit from the dictionary we have parsed if 'BIOMT' in info: ## biomt = info['BIOMT'] ## model.biounit = BU.BioUnit(model, biomt) ## del info['BIOMT'] model.info.update( info ) except: msg = self.__xplorAtomIndicesTest( source ) or ' ' raise PDBParserError('Cannot read ' + str(source) + ' as PDB\n'\ '\ERROR: ' + T.lastError() + msg) model.setSource( source )
def nextComplex(self): """ Take list of lines, extract all Hex info about one complex (Solution number, hex energy,..) also extract 16 numbers of the transformation matrix and put them into 4 by 4 numeric array. @return: Complex created from the output from Hex @rtype: Complex """ ## get set of lines describing next complex: lines = self._nextBlock() if lines is None: ## EOF return None ## skip incomplete records if len(lines) < 13: lines = self._nextBlock() ## fill info dictionary i = {} matrix = None for l in lines: try: ## labels has to be in the same order as in hex.out m = self.ex_line.search(l) if m is not None: m = m.groups() if m[0] == 'Orientation': i['hex_clst'] = int(m[1]) elif m[0] == 'Solution': i['soln'] = int(m[1]) elif m[0] == 'ReceptorModel': if self.forceModel: i['model1'] = self.forceModel[0] else: i['model1'] = int(m[1]) elif m[0] == 'LigandModel': if self.forceModel: i['model2'] = self.forceModel[1] else: i['model2'] = int(m[1]) elif m[0] == 'Bumps': if int(m[1]) != -1: i['bumps'] = int(m[1]) elif m[0] == 'ReferenceRMS': i['rms'] = float(m[1]) elif m[0] == 'Vshape': if float(m[1]) != 0.0: i['hex_Vshape'] = float(m[1]) elif m[0] == 'Vclash': if float(m[1]) != 0.0: i['hex_Vclash'] = float(m[1]) elif m[0] == 'Etotal': i['hex_etotal'] = float(m[1]) elif m[0] == 'Eshape': i['hex_eshape'] = float(m[1]) elif m[0] == 'Eair': i['hex_eair'] = float(m[1]) elif m[0] == 'LigandMatrix': ## get all numbers of matrix as list of strings strings = self.ex_matrix.findall(l) ## convert that to list of floats numbers = [] for each in strings: numbers += [float(each)] ## convert that to list of lists of 4 floats each matrix = [] for j in range(0, 4): matrix.append(numbers[4 * j:4 * (j + 1)]) ## create 4 by 4 Numeric array from 4 by 4 list matrix = N0.array(matrix, N0.Float32) except AttributeError: print("HexParser.nextComplex(): ", t.lastError()) ## Create new complex taking PCR models from dictionary c = Complex(self.rec_models[i['model1']], self.lig_models[i['model2']], matrix, i) return c
def __collectAll( self, fname, skipRes=None, headPatterns=[] ): """ Parse ATOM/HETATM lines from PDB. Collect coordinates plus dictionaries with the other pdb records of each atom. REMARK, HEADER, etc. lines are ignored. Some changes are made to the dictionary from PDBFile.readline():: - the 'position' entry (with the coordinates) is removed - leading and trailing spaces are removed from 'name' .. - .. but a 'name_original' entry keeps the old name with spaces - a 'type' entry is added. Its value is 'ATOM' or 'HETATM' - a 'after_ter' entry is added. Its value is 1, if atom is preceeded by a 'TER' line, otherwise 0 - empty 'element' entries are filled with the first non-number letter from the atom 'name' :param fname: name of pdb file :type fname: str :param skipRes: list with residue names that should be skipped :type skipRes: list of str :return: tuple of (1) dictionary of profiles and (2) xyz array N x 3 :rtype: ( list, array ) """ xyz = [] aProfs = {} info = {} in_header = True headPatterns = headPatterns or self.RE_REMARKS patterns = [ (key, re.compile(ex)) for key,ex in headPatterns ] for k in B.PDBModel.PDB_KEYS: aProfs[k] = list() f = IO.PDBFile( fname ) skipLine = False try: line, i = ('',''), 0 while line[0] != 'END' and line[0] != 'ENDMDL': i += 1 if not skipLine: try: line = f.readLine() except ValueError as what: self.log.add('Warning: Error parsing line %i of %s' % (i, T.stripFilename( fname )) ) self.log.add('\tError: '+str(what) ) continue else: skipLine = False ## header handling if in_header and line[0] == 'HEADER': info.update( self.__parseHeader( line ) ) if in_header and line[0] == 'REMARK': if line[1].startswith(' 350'): biomtDict, line = self.__parseBiomt( f, line ) info.update( biomtDict ) # we've hogged a line beyond REMARK 350 records in # __parseBiomt(), now we need to process it here skipLine = True continue else: info.update( self.__parseRemark( line, patterns ) ) ## preserve position of TER records newChain = line[0] == 'TER' if newChain: line = f.readLine() if (line[0] in ['ATOM','HETATM'] ): if in_header: in_header = False ## switch off HEADER parsing a = line[1] if skipRes and a['residue_name'] in skipRes: continue a['name_original'] = a['name'] a['name'] = a['name'].strip() a['type'] = line[0] if newChain: a['after_ter'] = 1 else: a['after_ter'] = 0 if a['element'] == '': a['element'] = self.__firstLetter( a['name'] ) xyz.append( a['position'] ) del a['position'] for k, v in a.items(): aProfs[k].append( v ) except: raise PDBParserError("Error parsing file "+fname+": " \ + T.lastError()) try: f.close() except: pass if len( xyz ) == 0: raise PDBParserError("Error parsing file "+fname+": "+ "Couldn't find any atoms.") return aProfs, N0.array( xyz, N0.Float32 ), info