downloaded from propka.ki.ku.dk

This commit is contained in:
Jimmy Charnley Kromann
2012-11-15 17:47:47 +01:00
parent 57be97bf6b
commit d882a2f5d1
25 changed files with 8673 additions and 2 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
# Compiled python files
*.py[cod]

View File

@@ -1,2 +1,40 @@
propka-3.1 # PROPKA 3.1
==========
See [propka.ki.ku.dk](http://propka.ki.ku.dk/)
for more information.
## Installation
No installation needed. Just clone and run.
## Requirements
* Python 3.1 or higher
## Getting started
1. Clone the code from GitHub
2. Run 'propka.py' with a .pdb file (see Examples)
## Examples
Calculate
propka.py 1hpx.pdb
If you don't have Python 3.1 installed, but
Python 3.2 (most Ubuntu distributions), then run
propka like;
python3.2 propka.py 1hpx.pdb
## Testing (for developers)
Please run test before pushing commits.

3
Source/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
#
__all__ = ['coupled_residues', 'lib', 'parameters', 'residue', 'bonds', 'debug', 'ligand', 'pdb', 'calculator', 'determinants', 'mutate', 'protein', 'chain', 'iterative', 'output', 'protonate']

387
Source/atom.py Normal file
View File

@@ -0,0 +1,387 @@
import string, Source.lib, Source.group
class Atom:
"""
Atom class - contains all atom information found in the pdbfile
"""
def __init__(self, line=None, verbose=False):
self.set_properties(line)
self.residue_label = "%-3s%4d%2s" % (self.name,self.resNumb, self.chainID)
self.groups_extracted = 0
self.group = None
self.group_type = None
self.number_of_bonded_elements = {}
self.cysteine_bridge = False
self.bonded_atoms = []
self.residue = None
self.conformation_container = None
self.molecular_container = None
self.is_protonated = False
self.steric_number_and_lone_pairs_set = False
self.terminal = None
self.charge = 0
self.charge_set = False
self.steric_number = 0
self.number_of_lone_pairs = 0
self.number_of_protons_to_add = 0
self.number_of_pi_electrons_in_double_and_triple_bonds = 0
self.number_of_pi_electrons_in_conjugate_double_and_triple_bonds = 0
# ligand atom types
self.sybyl_type = ''
self.sybyl_assigned = False
self.marvin_pka = False
return
def set_properties(self, line):
self.name = ''
self.numb = 0
self.x = 0.0
self.y = 0.0
self.z = 0.0
self.resNumb = 0
self.resName = ''
self.chainID = 'A'
self.type = ''
self.occ = '1.0'
self.beta = '0.0'
self.element = ''
self.icode = ''
if line:
self.name = line[12:16].strip()
self.numb = int( line[ 6:11].strip() )
self.x = float( line[30:38].strip() )
self.y = float( line[38:46].strip() )
self.z = float( line[46:54].strip() )
self.resNumb = int( line[22:26].strip() )
self.resName = "%-3s" % (line[17:20].strip())
self.chainID = max(line[21], 'A') # set chain id to A in case of white space
self.type = line[:6].strip().lower()
if self.resName in ['DA ','DC ','DG ','DT ']:
self.type = 'hetatm'
self.occ = line[55:60].strip()
self.beta = line[60:66].strip()
self.icode = line[26:27]
# Set the element using the position of the name in the pdb file
self.element = line[12:14].strip().strip(string.digits)
if len(self.name) == 4:
self.element = self.element[0]
if len(self.element)==2:
self.element = '%1s%1s'%(self.element[0], self.element[1].lower())
return
def set_group_type(self, type):
self.group_type = type
return
def count_bonded_elements(self, element):
res = 0
for ba in self.bonded_atoms:
if element == ba.element:
res +=1
return res
def get_bonded_elements(self, element):
res = []
for ba in self.bonded_atoms:
if ba.element == element:
res.append(ba)
return res
def get_bonded_heavy_atoms(self):
return [ba for ba in self.bonded_atoms if ba.element!='H']
def is_atom_within_bond_distance(self, other_atom, max_bonds, cur_bond):
""" check if <other_atom> is found within <max_bonds> bonds of self """
for ba in self.bonded_atoms:
if ba == other_atom:
return True
if max_bonds > cur_bond:
if ba.is_atom_within_bond_distance(other_atom, max_bonds, cur_bond+1):
return True
return False
def setProperty(self,
numb = None,
name = None,
resName = None,
chainID = None,
resNumb = None,
x = None,
y = None,
z = None,
occ = None,
beta = None):
"""
sets properties of the atom object
"""
if numb != None: self.numb = numb
if name != None: self.name = name
if resName != None: self.resName = resName
if chainID != None: self.chainID = chainID
if resNumb != None: self.resNumb = resNumb
if x != None: self.x = x
if y != None: self.y = y
if z != None: self.z = z
if occ != None: self.occ = occ
if beta != None: self.beta = beta
def makeCopy(self):
"""
making a copy of this atom
"""
newAtom = Atom()
newAtom.type = self.type
newAtom.numb = self.numb
newAtom.name = self.name
newAtom.element = self.element
newAtom.resName = self.resName
newAtom.resNumb = self.resNumb
newAtom.chainID = self.chainID
newAtom.x = self.x
newAtom.y = self.y
newAtom.z = self.z
newAtom.occ = self.occ
newAtom.beta = self.beta
newAtom.terminal = self.terminal
newAtom.residue_label = self.residue_label
newAtom.icode = self.icode
return newAtom
def make_input_line(self):
# making string
group = '-'
model_pka = '-'
if self.group:
group = self.group.type
if self.terminal == 'C-':
group = 'C-' ## circumventing C-/COO parameter unification
if self.group.titratable:
model_pka = '%6.2f'%self.group.model_pka
str = "%-6s%5d %s %s%2s%4d%12.3lf%8.3lf%8.3lf%6s%6s \n" % (self.type.upper(),
self.numb,
Source.lib.makeTidyAtomLabel(self.name,
self.element),
self.resName,
self.chainID,
self.resNumb,
self.x,
self.y,
self.z,
group,
model_pka)
return str
def make_conect_line(self):
res = 'CONECT%5d'%self.numb
# extract and sort numbers of bonded residues
bonded = []
for atom in self.bonded_atoms:
bonded.append(atom.numb)
bonded.sort()
# write 'em out
for b in bonded:
res += '%5d'%b
res += '\n'
return res
def get_input_parameters(self):
""" Method for getting the input parameters stored in the
occupancy and b-factor fields in input files"""
# Set the group type
if self.occ != '-':
# make sure to set the terminal
if self.occ in ['N+','C-']:
self.terminal = self.occ
# save the ligand group charge
if self.occ =='BLG':
self.charge=+1
elif self.occ =='ALG':
self.charge=-1
# generic ions
if self.occ in ['1P','2P','1N','2N']:
self.resName=self.occ
self.occ='Ion'
# correct the group type
self.occ = self.occ.replace('N+','Nterm')
self.occ = self.occ.replace('C-','Cterm')
self.occ = self.occ.replace('ION','Ion')
self.occ = self.occ.replace('ALG','titratable_ligand')
self.occ = self.occ.replace('BLG','titratable_ligand')
self.occ = self.occ.replace('LG','non_titratable_ligand')
# try to initialise the group
try:
exec('self.group = Source.group.%s_group(self)'%self.occ)
except:
raise Exception('%s in input_file is not recognized as a group'%self.occ)
# set the model pKa value
if self.beta != '-':
self.group.model_pka = float(self.beta)
self.group.model_pka_set = True
# set occ and beta to standard values
self.occ = '1.00'
self.beta = '0.00'
return
def make_pdb_line(self):
# making string
str = "%-6s%5d %s %s%2s%4d%12.3lf%8.3lf%8.3lf%6s%6s\n" % (self.type.upper(),
self.numb,
Source.lib.makeTidyAtomLabel(self.name,
self.element),
self.resName,
self.chainID,
self.resNumb,
self.x,
self.y,
self.z,
self.occ,
self.beta)
return str
def make_mol2_line(self,id):
#1 S1 3.6147 2.0531 1.4795 S.3 1 noname -0.1785
# making string
str = "%-4d %-4s %10.4f %10.4f %10.4f %6s %6d %10s %10.4f\n" % (id,
Source.lib.makeTidyAtomLabel(self.name,
self.element),
self.x,
self.y,
self.z,
self.sybyl_type.replace('-',''),
self.resNumb,
self.resName,
0.0)#self.charge)
return str
def makePDBLine(self,
numb = None,
name = None,
resName = None,
chainID = None,
resNumb = None,
x = None,
y = None,
z = None,
occ = None,
beta = None):
"""
returns a pdb ATOM-line for various purposes;
specifying arguments over-writes.
"""
if numb == None: numb = self.numb
if name == None: name = self.name
if resName == None: resName = self.resName
if chainID == None: chainID = self.chainID
if resNumb == None: resNumb = self.resNumb
if x == None: x = self.x
if y == None: y = self.y
if z == None: z = self.z
if occ == None: occ = self.occ
if beta == None: beta = self.beta
# making string
str = "ATOM "
str += "%6d" % (numb)
str += " %s" % (Source.lib.makeTidyAtomLabel(name,self.element))
str += " %s" % (resName)
str += "%2s" % (chainID)
str += "%4d" % (resNumb)
str += "%12.3lf" % (x)
str += "%8.3lf" % (y)
str += "%8.3lf" % (z)
str += "%6.2lf" % (occ)
str += "%6.2lf" % (beta)
str += '\n'
return str
def getTidyLabel(self):
"""
Returns a 'tidier' atom label for printing the new pdbfile
"""
return Source.lib.makeTidyAtomLabel(self.name,self.element)
def __str__(self):
return '%5d-%4s %5d-%3s (%1s) [%8.3f %8.3f %8.3f] %s' %(self.numb, self.name, self.resNumb, self.resName, self.chainID, self.x, self.y, self.z,self.element)
# def get_element(self):
# """ try to extract element if not already done"""
# if self.element == '':
# self.element = self.name.strip(string.digits)[0]
# return self.element
def set_residue(self, residue):
""" Makes a references to the parent residue"""
if self.residue == None:
self.residue = residue

485
Source/bonds.py Normal file
View File

@@ -0,0 +1,485 @@
import pickle,sys,os,math,Source.calculations
class bondmaker:
def __init__(self):
# predefined bonding distances
self.distances = {
'S-S':2.5,
'F-F':1.7}
self.distances_squared = {}
for k in self.distances.keys():
self.distances_squared[k]=self.distances[k]*self.distances[k]
self.H_dist = 1.5;
self.default_dist = 2.0;
self.H_dist_squared = self.H_dist * self.H_dist
self.default_dist_squared = self.default_dist * self.default_dist
self.max_sq_distance = max(list(self.distances_squared.values())+[self.default_dist_squared])
# protein bonding data
path = os.path.split(__file__)[0]
self.data_file_name = os.path.join(path,'protein_bonds.dat')
data = open(self.data_file_name,'rb')
self.protein_bonds = pickle.load(data)
data.close()
self.intra_residue_backbone_bonds = {'N': ['CA'],
'CA':['N','C'],
'C': ['CA','O'],
'O': ['C']}
self.number_of_pi_electrons_in_bonds_in_backbone = {'C':1,
'O':1}
self.number_of_pi_electrons_in_conjugate_bonds_in_backbone = {'N':1}
self.number_of_pi_electrons_in_bonds_in_sidechains = {'ARG-CZ' :1,
'ARG-NH1':1,
'ASN-OD1':1,
'ASN-CG' :1,
'ASP-OD1':1,
'ASP-CG' :1,
'GLU-OE1':1,
'GLU-CD' :1,
'GLN-OE1':1,
'GLN-CD' :1,
'HIS-CG' :1,
'HIS-CD2':1,
'HIS-ND1':1,
'HIS-CE1':1,
'PHE-CG' :1,
'PHE-CD1':1,
'PHE-CE1':1,
'PHE-CZ' :1,
'PHE-CE2':1,
'PHE-CD2':1,
'TRP-CG' :1,
'TRP-CD1':1,
'TRP-CE2':1,
'TRP-CD2':1,
'TRP-CE3':1,
'TRP-CZ3':1,
'TRP-CH2':1,
'TRP-CZ2':1,
'TYR-CG' :1,
'TYR-CD1':1,
'TYR-CE1':1,
'TYR-CZ' :1,
'TYR-CE2':1,
'TYR-CD2':1}
self.number_of_pi_electrons_in_conjugate_bonds_in_sidechains = {'ARG-NE' :1,
'ARG-NH2':1,
'ASN-ND2':1,
'GLN-NE2':1,
'HIS-NE2':1,
'TRP-NE1':1}
self.number_of_pi_electrons_in_bonds_ligands = {'C.ar':1,
'N.pl3':0,
'C.2':1,
'O.2':1,
'O.co2':1,
'N.ar':1,
'C.1':2,
'N.1':2}
self.number_of_pi_electrons_in_conjugate_bonds_in_ligands = {'N.am':1,'N.pl3':1}
self.backbone_atoms = list(self.intra_residue_backbone_bonds.keys())
self.terminal_oxygen_names = ['OXT','O\'\'']
return
def find_bonds_for_protein(self, protein):
""" Finds bonds proteins based on the way atoms
normally bond in proteins"""
print('++++ Side chains ++++')
# side chains
for chain in protein.chains:
for residue in chain.residues:
if residue.resName.replace(' ','') not in ['N+','C-']:
self.find_bonds_for_side_chain(residue.atoms)
print('++++ Backbones ++++')
# backbone
last_residues = []
for chain in protein.chains:
for i in range(1,len(chain.residues)):
if chain.residues[i-1].resName.replace(' ','') not in ['N+','C-']:
if chain.residues[i].resName.replace(' ','') not in ['N+','C-']:
self.connect_backbone(chain.residues[i-1], chain.residues[i])
last_residues.append(chain.residues[i])
print('++++ terminal oxygen ++++')
# terminal OXT
for last_residue in last_residues:
self.find_bonds_for_terminal_oxygen(last_residue)
print('++++ cysteines ++++')
# Cysteines
for chain in protein.chains:
for i in range(0,len(chain.residues)):
if chain.residues[i].resName == 'CYS':
for j in range(0,len(chain.residues)):
if chain.residues[j].resName == 'CYS' and j != i:
self.check_for_cysteine_bonds(chain.residues[i],
chain.residues[j])
return
def check_for_cysteine_bonds(self, cys1, cys2):
for atom1 in cys1.atoms:
if atom1.name == 'SG':
for atom2 in cys2.atoms:
if atom2.name == 'SG':
if Source.calculations.squared_distance(atom1,atom2) < self.SS_dist_squared:
self.make_bond(atom1, atom2)
return
def find_bonds_for_terminal_oxygen(self, residue):
for atom1 in residue.atoms:
if atom1.name in self.terminal_oxygen_names:
for atom2 in residue.atoms:
if atom2.name == 'C':
self.make_bond(atom1, atom2)
return
def connect_backbone(self, residue1, residue2):
""" Sets up bonds in the backbone """
# residue 1
self.find_bonds_for_residue_backbone(residue1)
# residue 2
self.find_bonds_for_residue_backbone(residue2)
# inter-residue bond
for atom1 in residue1.atoms:
if atom1.name == 'C':
for atom2 in residue2.atoms:
if atom2.name == 'N':
if Source.calculations.squared_distance(atom1,atom2) < self.default_dist_squared:
self.make_bond(atom1, atom2)
return
def find_bonds_for_residue_backbone(self, residue):
for atom1 in residue.atoms:
if atom1.name in list(self.number_of_pi_electrons_in_bonds_in_backbone.keys()):
atom1.number_of_pi_electrons_in_double_and_triple_bonds = self.number_of_pi_electrons_in_bonds_in_backbone[atom1.name]
if atom1.name in list(self.number_of_pi_electrons_in_conjugate_bonds_in_backbone.keys()) and len(atom1.bonded_atoms)>1: # last part to avoid including N-term
atom1.number_of_pi_electrons_in_conjugate_double_and_triple_bonds = self.number_of_pi_electrons_in_conjugate_bonds_in_backbone[atom1.name]
if atom1.name in self.backbone_atoms:
for atom2 in residue.atoms:
if atom2.name in self.intra_residue_backbone_bonds[atom1.name]:
self.make_bond(atom1, atom2)
return
def find_bonds_for_side_chain(self, atoms):
""" Finds bonds for a side chain """
for atom1 in atoms:
key = '%s-%s'%(atom1.resName,atom1.name)
if key in list(self.number_of_pi_electrons_in_bonds_in_sidechains.keys()):
atom1.number_of_pi_electrons_in_double_and_triple_bonds = self.number_of_pi_electrons_in_bonds_in_sidechains[key]
if key in list(self.number_of_pi_electrons_in_conjugate_bonds_in_sidechains.keys()):
atom1.number_of_pi_electrons_in_conjugate_double_and_triple_bonds = self.number_of_pi_electrons_in_conjugate_bonds_in_sidechains[key]
if not atom1.name in self.backbone_atoms:
if not atom1.name in self.terminal_oxygen_names:
for atom2 in atoms:
if atom2.name in self.protein_bonds[atom1.resName][atom1.name]:
self.make_bond(atom1,atom2)
return
def find_bonds_for_ligand(self, ligand):
""" Finds bonds for all atoms in the molecule """
# identify bonding atoms
self.find_bonds_for_atoms(ligand.atoms)
self.add_pi_electron_table_info(ligand.atoms)
return
def add_pi_electron_table_info(self, atoms):
# apply table information on pi-electrons
for atom in atoms:
# for ligands
if atom.type == 'hetatm':
if atom.sybyl_type in self.number_of_pi_electrons_in_bonds_ligands.keys():
atom.number_of_pi_electrons_in_double_and_triple_bonds = self.number_of_pi_electrons_in_bonds_ligands[atom.sybyl_type]
if atom.sybyl_type in self.number_of_pi_electrons_in_conjugate_bonds_in_ligands.keys():
atom.number_of_pi_electrons_in_conjugate_double_and_triple_bonds = self.number_of_pi_electrons_in_conjugate_bonds_in_ligands[atom.sybyl_type]
# for protein
if atom.type == 'atom':
key = '%s-%s'%(atom.resName,atom.name)
if key in list(self.number_of_pi_electrons_in_bonds_in_sidechains.keys()):
atom.number_of_pi_electrons_in_double_and_triple_bonds = self.number_of_pi_electrons_in_bonds_in_sidechains[key]
if key in list(self.number_of_pi_electrons_in_conjugate_bonds_in_sidechains.keys()):
atom.number_of_pi_electrons_in_conjugate_double_and_triple_bonds = self.number_of_pi_electrons_in_conjugate_bonds_in_sidechains[key]
if atom.name in list(self.number_of_pi_electrons_in_bonds_in_backbone.keys()):
atom.number_of_pi_electrons_in_double_and_triple_bonds = self.number_of_pi_electrons_in_bonds_in_backbone[atom.name]
if atom.name in list(self.number_of_pi_electrons_in_conjugate_bonds_in_backbone.keys()) and len(atom.bonded_atoms)>1: # last part to avoid including N-term
atom.number_of_pi_electrons_in_conjugate_double_and_triple_bonds = self.number_of_pi_electrons_in_conjugate_bonds_in_backbone[atom.name]
return
def find_bonds_for_protein_by_distance(self, molecule):
""" Finds bonds for all atoms in the molecule """
#self.find_bonds_for_protein(molecule)
atoms = []
for chain in molecule.chains:
for residue in chain.residues:
if residue.resName.replace(' ','') not in ['N+','C-']:
for atom in residue.atoms:
atoms.append(atom)
self.find_bonds_for_atoms_using_boxes(atoms) #####
#self.find_bonds_for_atoms(atoms) #####
return atoms
def find_bonds_for_atoms(self, atoms):
""" Finds all bonds for a list of atoms"""
no_atoms = len(atoms)
for i in range(no_atoms):
for j in range(i+1,no_atoms):
if atoms[i] in atoms[j].bonded_atoms:
continue
if self.check_distance(atoms[i], atoms[j]):
self.make_bond(atoms[i],atoms[j])
# di-sulphide bonds
if atoms[i].element == 'S' and atoms[j].element == 'S':
atoms[i].cysteine_bridge = True
atoms[j].cysteine_bridge = True
return
def check_distance(self, atom1, atom2):
sq_dist = Source.calculations.squared_distance(atom1, atom2)
if sq_dist > self.max_sq_distance:
return False
key = '%s-%s'%(atom1.element,atom2.element)
h_count = key.count('H')
if sq_dist < self.H_dist_squared and h_count==1:
return True
if sq_dist < self.default_dist_squared and h_count==0:
return True
if key in self.distances_squared.keys():
if sq_dist < self.distances_squared[key]:
return True
return False
def find_bonds_for_molecules_using_boxes(self, molecules):
""" Finds all bonds for a molecular container"""
for name in molecules.conformation_names:
self.find_bonds_for_atoms_using_boxes(molecules.conformations[name].atoms)
#for chain in molecules.conformations[name].chains:
# self.find_bonds_for_atoms_using_boxes(molecules.conformations[name].get_chain(chain))
return
def add_pi_electron_information(self, molecules):
for name in molecules.conformation_names:
self.add_pi_electron_table_info(molecules.conformations[name].atoms)
return
def find_bonds_for_atoms_using_boxes(self, atoms):
""" Finds all bonds for a list of atoms"""
box_size = 2.5
# find min and max coordinates
xmin = 1e6; xmax = -1e6
ymin = 1e6; ymax = -1e6
zmin = 1e6; zmax = -1e6
for atom in atoms:
if atom.x > xmax:
xmax = atom.x
if atom.y > ymax:
ymax = atom.y
if atom.z > zmax:
zmax = atom.z
if atom.x < xmin:
xmin = atom.x
if atom.y < ymin:
ymin = atom.y
if atom.z < zmin:
zmin = atom.z
xlen = xmax-xmin
ylen = ymax-ymin
zlen = zmax-zmin
#print('x range: [%6.2f;%6.2f] %6.2f'%(xmin,xmax,xlen))
#print('y range: [%6.2f;%6.2f] %6.2f'%(ymin,ymax,ylen))
#print('z range: [%6.2f;%6.2f] %6.2f'%(zmin,zmax,zlen))
# how many boxes do we need in each dimension?
self.no_box_x = max(1, math.ceil(xlen/box_size))
self.no_box_y = max(1, math.ceil(ylen/box_size))
self.no_box_z = max(1, math.ceil(zlen/box_size))
#print('No. box x: %6.2f'%self.no_box_x)
#print('No. box y: %6.2f'%self.no_box_y)
#print('No. box z: %6.2f'%self.no_box_z)
# initialize boxes
self.boxes = {}
for x in range(self.no_box_x):
for y in range(self.no_box_y):
for z in range(self.no_box_z):
self.boxes[self.box_key(x,y,z)] = []
# put atoms into boxes
for atom in atoms:
x = math.floor((atom.x-xmin)/box_size)
y = math.floor((atom.y-ymin)/box_size)
z = math.floor((atom.z-zmin)/box_size)
self.put_atom_in_box(x,y,z,atom)
# assign bonds
keys = self.boxes.keys()
for key in keys:
self.find_bonds_for_atoms(self.boxes[key])
return
def put_atom_in_box(self,x,y,z,atom):
# atom in the x,y,z box and the up to 7 neighboring boxes on
# one side of the x,y,z box in each dimension
for bx in [x,x+1]:
for by in [y,y+1]:
for bz in [z,z+1]:
key = self.box_key(bx,by,bz)
if key in self.boxes.keys():
self.boxes[key].append(atom)
#print(atom,'->',key,':',len(self.boxes[key]))
return
def box_key(self, x, y, z):
return '%d-%d-%d'%(x,y,z)
def has_bond(self, atom1, atom2):
if atom1 in atom2.bonded_atoms or atom2 in atom1.bonded_atoms:
return True
return False
def make_bond(self, atom1, atom2):
""" Makes a bond between atom1 and atom2 """
if atom1 == atom2:
return
#print('making bond for',atom1,atom2)
if not atom1 in atom2.bonded_atoms:
atom2.bonded_atoms.append(atom1)
if not atom2 in atom1.bonded_atoms:
atom1.bonded_atoms.append(atom2)
return
def generate_protein_bond_dictionary(self, atoms):
for atom in atoms:
for bonded_atom in atom.bonded_atoms:
resi_i = atom.resName
name_i = atom.name
resi_j = bonded_atom.resName
name_j = bonded_atom.name
if not name_i in self.backbone_atoms or\
not name_j in self.backbone_atoms:
if not name_i in self.terminal_oxygen_names and\
not name_j in self.terminal_oxygen_names:
if not resi_i in list(self.protein_bonds.keys()):
self.protein_bonds[resi_i] = {}
if not name_i in self.protein_bonds[resi_i]:
self.protein_bonds[resi_i][name_i] = []
if not name_j in self.protein_bonds[resi_i][name_i]:
self.protein_bonds[resi_i][name_i].append(name_j)
if not resi_j in list(self.protein_bonds.keys()):
self.protein_bonds[resi_j] = {}
if not name_j in self.protein_bonds[resi_j]:
self.protein_bonds[resi_j][name_j] = []
if not name_i in self.protein_bonds[resi_j][name_j]:
self.protein_bonds[resi_j][name_j].append(name_i)
return
if __name__ == '__main__':
# If called directly, set up protein bond dictionary
import protein, pdb, sys,os
arguments = sys.argv
if len(arguments) != 2:
print('Usage: bonds.py <pdb_file>')
sys.exit(0)
filename = arguments[1]
if not os.path.isfile(filename):
print('Error: Could not find \"%s\"'%filename)
sys.exit(1)
pdblist = pdb.readPDB(filename)
my_protein = protein.Protein(pdblist,'test.pdb')
for chain in my_protein.chains:
for residue in chain.residues:
residue.atoms = [atom for atom in residue.atoms if atom.element != 'H']
b = bondmaker()
#b.protein_bonds = {}
atoms = b.find_bonds_for_protein_by_distance(my_protein)
# b.generate_protein_bond_dictionary(atoms)
#file = open(b.data_file_name,'wb')
#pickle.dump(b.protein_bonds, file)
#file.close()

891
Source/calculations.py Normal file
View File

@@ -0,0 +1,891 @@
import math, Source.protonate, Source.bonds,copy, sys
#
# methods for setting up atom naming, bonding, and protonation
#
def setup_bonding_and_protonation(parameters, molecular_container):
# make bonds
my_bond_maker = setup_bonding(parameters, molecular_container)
# set up ligand atom names
set_ligand_atom_names(molecular_container)
# apply information on pi electrons
my_bond_maker.add_pi_electron_information(molecular_container)
# Protonate atoms
if molecular_container.options.protonate_all:
my_protonator = Source.protonate.Protonate(verbose=False)
my_protonator.protonate(molecular_container)
return
def setup_bonding(parameters, molecular_container):
# make bonds
my_bond_maker = Source.bonds.bondmaker()
my_bond_maker.find_bonds_for_molecules_using_boxes(molecular_container)
return my_bond_maker
def set_ligand_atom_names(molecular_container):
for name in molecular_container.conformation_names:
molecular_container.conformations[name].set_ligand_atom_names()
return
#
# The following methods imitates propka3.0 protonation!
#
def setup_bonding_and_protonation_30_style(parameters, molecular_container):
# Protonate atoms
protonate_30_style(molecular_container)
# make bonds
my_bond_maker = Source.bonds.bondmaker()
my_bond_maker.find_bonds_for_molecules_using_boxes(molecular_container)
return
def protonate_30_style(molecular_container):
for name in molecular_container.conformation_names:
print('Now protonating',name)
# split atom into residues
curres = -1000000
residue = []
O=None
C=None
for atom in molecular_container.conformations[name].atoms:
if atom.resNumb != curres:
curres = atom.resNumb
if len(residue)>0:
#backbone
[O, C]= addBackBoneHydrogen(residue,O,C)
#arginine
if residue[0].resName == 'ARG':
addArgHydrogen(residue)
#histidine
if residue[0].resName == 'HIS':
addHisHydrogen(residue)
#tryptophan
if residue[0].resName == 'TRP':
addTrpHydrogen(residue)
#amides
if residue[0].resName in ['GLN','ASN']:
addAmdHydrogen(residue)
residue = []
if atom.type=='atom':
residue.append(atom)
return
def addArgHydrogen(residue):
"""
Adds Arg hydrogen atoms to residues according to the 'old way'.
"""
#print('Adding arg H',residue)
for atom in residue:
if atom.name == "CD":
CD = atom
elif atom.name == "CZ":
CZ = atom
elif atom.name == "NE":
NE = atom
elif atom.name == "NH1":
NH1 = atom
elif atom.name == "NH2":
NH2 = atom
H1 = protonateSP2([CD, NE, CZ])
H1.name = "HE"
H2 = protonateDirection([NH1, NE, CZ])
H2.name = "HN1"
H3 = protonateDirection([NH1, NE, CD])
H3.name = "HN2"
H4 = protonateDirection([NH2, NE, CZ])
H4.name = "HN3"
H5 = protonateDirection([NH2, NE, H1])
H5.name = "HN4"
return [H1,H2,H3,H4,H5]
def addHisHydrogen(residue):
"""
Adds His hydrogen atoms to residues according to the 'old way'.
"""
for atom in residue:
if atom.name == "CG":
CG = atom
elif atom.name == "ND1":
ND = atom
elif atom.name == "CD2":
CD = atom
elif atom.name == "CE1":
CE = atom
elif atom.name == "NE2":
NE = atom
HD = protonateSP2([CG, ND, CE])
HD.name = "HND"
HE = protonateSP2([CD, NE, CE])
HE.name = "HNE"
return
def addTrpHydrogen(residue):
"""
Adds Trp hydrogen atoms to residues according to the 'old way'.
"""
CD = None
NE = None
DE = None
for atom in residue:
if atom.name == "CD1":
CD = atom
elif atom.name == "NE1":
NE = atom
elif atom.name == "CE2":
CE = atom
if CD == None or NE == None or CE == None:
str = "Did not find all atoms in %s%4d - in %s" % (self.resName, self.resNumb, "addTrpHydrogen()")
print(str)
sys.exit(0)
HE = protonateSP2([CD, NE, CE])
HE.name = "HNE"
return
def addAmdHydrogen(residue):
"""
Adds Gln & Asn hydrogen atoms to residues according to the 'old way'.
"""
C = None
O = None
N = None
for atom in residue:
if (atom.resName == "GLN" and atom.name == "CD") or (atom.resName == "ASN" and atom.name == "CG"):
C = atom
elif (atom.resName == "GLN" and atom.name == "OE1") or (atom.resName == "ASN" and atom.name == "OD1"):
O = atom
elif (atom.resName == "GLN" and atom.name == "NE2") or (atom.resName == "ASN" and atom.name == "ND2"):
N = atom
if C == None or O == None or N == None:
str = "Did not find N, C and/or O in %s%4d - in %s" % (atom.resName, atom.resNumb, "addAmdHydrogen()")
print(str)
sys.exit(0)
H1 = protonateDirection([N, O, C])
H1.name = "HN1"
H2 = protonateAverageDirection([N, C, O])
H2.name = "HN2"
return
def addBackBoneHydrogen(residue, O, C):
"""
Adds hydrogen backbone atoms to residues according to the old way; dR is wrong for the N-terminus
(i.e. first residue) but it doesn't affect anything at the moment. Could be improved, but works
for now.
"""
new_C = None
new_O = None
N = None
for atom in residue:
if atom.name == "N":
N = atom
if atom.name == "C":
new_C = atom
if atom.name == "O":
new_O = atom
if None in [C,O,N]:
return [new_O,new_C]
if N.resName == "PRO":
""" PRO doesn't have an H-atom; do nothing """
else:
H = protonateDirection([N, O, C])
H.name = "H"
return [new_O,new_C]
def protonateDirection(list):
"""
Protonates an atom, X1, given a direction (X2 -> X3) [X1, X2, X3]
"""
X1 = list[0]
X2 = list[1]
X3 = list[2]
dX = (X3.x - X2.x)
dY = (X3.y - X2.y)
dZ = (X3.z - X2.z)
length = math.sqrt( dX*dX + dY*dY + dZ*dZ )
x = X1.x + dX/length
y = X1.y + dY/length
z = X1.z + dZ/length
H = make_new_H(X1,x,y,z)
H.name = "H"
return H
def protonateAverageDirection(list):
"""
Protonates an atom, X1, given a direction (X1/X2 -> X3) [X1, X2, X3]
Note, this one uses the average of X1 & X2 (N & O) unlike the previous
N - C = O
"""
X1 = list[0]
X2 = list[1]
X3 = list[2]
dX = (X3.x + X1.x)*0.5 - X2.x
dY = (X3.y + X1.y)*0.5 - X2.y
dZ = (X3.z + X1.z)*0.5 - X2.z
length = math.sqrt( dX*dX + dY*dY + dZ*dZ )
x = X1.x + dX/length
y = X1.y + dY/length
z = X1.z + dZ/length
H = make_new_H(X1,x,y,z)
H.name = "H"
return H
def protonateSP2(list):
"""
Protonates a SP2 atom, H2, given a list of [X1, X2, X3]
X1-X2-X3
"""
X1 = list[0]
X2 = list[1]
X3 = list[2]
dX = (X1.x + X3.x)*0.5 - X2.x
dY = (X1.y + X3.y)*0.5 - X2.y
dZ = (X1.z + X3.z)*0.5 - X2.z
length = math.sqrt( dX*dX + dY*dY + dZ*dZ )
x = X2.x - dX/length
y = X2.y - dY/length
z = X2.z - dZ/length
H = make_new_H(X2,x,y,z)
H.name = "H"
return H
def make_new_H(atom, x,y,z):
new_H = Source.atom.Atom()
new_H.setProperty(numb = None,
name = 'H%s'%atom.name[1:],
resName = atom.resName,
chainID = atom.chainID,
resNumb = atom.resNumb,
x = x,
y = y,
z = z,
occ = None,
beta = None)
new_H.element = 'H'
new_H.bonded_atoms = [atom]
new_H.charge = 0
new_H.steric_number = 0
new_H.number_of_lone_pairs = 0
new_H.number_of_protons_to_add = 0
new_H.number_of_pi_electrons_in_double_and_triple_bonds = 0
atom.bonded_atoms.append(new_H)
atom.conformation_container.add_atom(new_H)
return new_H
######## 3.0 style protonation methods end
#
# Desolvation methods
#
def radial_volume_desolvation(parameters, group):
all_atoms = group.atom.conformation_container.get_non_hydrogen_atoms()
volume = 0.0
group.Nmass = 0
min_distance_4th = 57.1914 # pow(2.75, 4)
for atom in all_atoms:
# ignore atoms in the same residue
if atom.resNumb == group.atom.resNumb and atom.chainID == group.atom.chainID:
continue
sq_dist = squared_distance(group, atom)
# desolvation
if sq_dist < parameters.desolv_cutoff_squared:
# use a default relative volume of 1.0 if the volume of the element is not found in parameters
dv = 1.0
if atom.element in parameters.VanDerWaalsVolume.keys():
dv = parameters.VanDerWaalsVolume[atom.element]
# insert check for methyl groups
if atom.element == 'C' and atom.name not in ['CA','C']:
dv = parameters.VanDerWaalsVolume['C4']
dv_inc = dv/max(min_distance_4th, sq_dist*sq_dist)
# dv_inc = dv/(sq_dist*sq_dist) - dv/(parameters.desolv_cutoff_squared*parameters.desolv_cutoff_squared)
volume += dv_inc
# buried
if sq_dist < parameters.buried_cutoff_squared:
group.Nmass += 1
group.buried = calculate_weight(parameters, group.Nmass)
scale_factor = calculate_scale_factor(parameters, group.buried)
volume_after_allowance = max(0.00, volume-parameters.desolvationAllowance)
group.Emass = group.charge * parameters.desolvationPrefactor * volume_after_allowance * scale_factor
# Emass, Nmass
# Elocl, Nlocl -> reorganisation energy (count backbone hydorgen bond acceptors, C=O)
#print('%s %5.2f %5.2f %4d'%(group, group.buried, group.Emass, group.Nmass))
return
def contactDesolvation(parameters, group):
"""
calculates the desolvation according to the Contact Model, the old default
"""
local_radius = {'ASP': 4.5,
'GLU': 4.5,
'HIS': 4.5,
'CYS': 3.5,
'TYR': 3.5,
'LYS': 4.5,
'ARG': 5.0,
'C-': 4.5,
'N+': 4.5}
all_atoms = group.atom.conformation_container.get_non_hydrogen_atoms()
if residue.resName in version.desolvationRadii:
local_cutoff = version.desolvationRadii[residue.resName]
else:
local_cutoff = 0.00
residue.Nmass = 0
residue.Nlocl = 0
for atom in all_atoms:
if atom.resNumb != group.atom.resNumb or atom.chainID != group.atom.chainID:
dX = atom.x - residue.x
dY = atom.y - residue.y
dZ = atom.z - residue.z
distance = math.sqrt(dX*dX + dY*dY + dZ*dZ)
if distance < local_cutoff:
group.Nlocl += 1
if distance < parameters.buried_cutoff:
group.Nmass += 1
if residue.Nmass > 400:
group.location = "BURIED "
else:
group.location = "SURFACE"
group.Emass = group.charge * parameters.desolvationPrefactor * max(0.00, group.Nmass-parameters.desolvationAllowance)
group.Elocl = group.charge * parameters.desolvationLocal * group.Nlocl
# Buried ratio - new feature in propka3.0
# Note, there will be an unforseen problem: e.g. if one residue has Nmass > Nmax and
# the other Nmass < Nmax, the Npair will not be Nmass1 + Nmass2!
residue.buried = calculateWeight(residue.Nmass)
return 0.00, 0.00, 0.00, 0.00
def calculate_scale_factor(parameters, weight):
scale_factor = 1.0 - (1.0 - parameters.desolvationSurfaceScalingFactor)*(1.0 - weight)
return scale_factor
def calculate_weight(parameters, Nmass):
"""
calculating the atom-based desolvation weight
"""
weight = float(Nmass - parameters.Nmin)/float(parameters.Nmax - parameters.Nmin)
weight = min(1.0, weight)
weight = max(0.0, weight)
return weight
def squared_distance(atom1, atom2):
# if atom1 in atom2.squared_distances:
# return atom2.squared_distances[atom1]
dx = atom2.x - atom1.x
dy = atom2.y - atom1.y
dz = atom2.z - atom1.z
res = dx*dx+dy*dy+dz*dz
return res
def distance(atom1, atom2):
return math.sqrt(squared_distance(atom1,atom2))
def get_smallest_distance(atoms1, atoms2):
res_distance =1e6
res_atom1 = None
res_atom2 = None
for atom1 in atoms1:
for atom2 in atoms2:
dist = squared_distance(atom1, atom2)
if dist < res_distance:
res_distance = dist
res_atom1 = atom1
res_atom2 = atom2
return [res_atom1, math.sqrt(res_distance), res_atom2]
def calculatePairWeight(parameters, Nmass1, Nmass2):
"""
calculating the atom-pair based desolvation weight
"""
Nmass = Nmass1 + Nmass2
Nmin = 2*parameters.Nmin
Nmax = 2*parameters.Nmax
weight = float(Nmass - Nmin)/float(Nmax - Nmin)
weight = min(1.0, weight)
weight = max(0.0, weight)
return weight
def HydrogenBondEnergy(distance, dpka_max, cutoff, f_angle=1.0):
"""
returns a hydrogen-bond interaction pKa shift
"""
if distance < cutoff[0]:
value = 1.00
elif distance > cutoff[1]:
value = 0.00
else:
value = 1.0-(distance-cutoff[0])/(cutoff[1]-cutoff[0])
dpKa = dpka_max*value*f_angle
return abs(dpKa)
def AngleFactorX(atom1=None, atom2=None, atom3=None, center=None):
"""
Calculates the distance and angle-factor from three atoms for back-bone interactions,
IMPORTANT: you need to use atom1 to be the e.g. ASP atom if distance is reset at return: [O1 -- H2-N3]
Also generalized to be able to be used for residue 'centers' for C=O COO interactions.
"""
dX_32 = atom2.x - atom3.x
dY_32 = atom2.y - atom3.y
dZ_32 = atom2.z - atom3.z
distance_23 = math.sqrt( dX_32*dX_32 + dY_32*dY_32 + dZ_32*dZ_32 )
dX_32 = dX_32/distance_23
dY_32 = dY_32/distance_23
dZ_32 = dZ_32/distance_23
if atom1 == None:
dX_21 = center[0] - atom2.x
dY_21 = center[1] - atom2.y
dZ_21 = center[2] - atom2.z
else:
dX_21 = atom1.x - atom2.x
dY_21 = atom1.y - atom2.y
dZ_21 = atom1.z - atom2.z
distance_12 = math.sqrt( dX_21*dX_21 + dY_21*dY_21 + dZ_21*dZ_21 )
dX_21 = dX_21/distance_12
dY_21 = dY_21/distance_12
dZ_21 = dZ_21/distance_12
f_angle = dX_21*dX_32 + dY_21*dY_32 + dZ_21*dZ_32
return distance_12, f_angle, distance_23
def hydrogen_bond_interaction(group1, group2, version):
# find the smallest distance between interacting atoms
atoms1 = group1.get_interaction_atoms(group2)
atoms2 = group2.get_interaction_atoms(group1)
[closest_atom1, distance, closest_atom2] = Source.calculations.get_smallest_distance(atoms1, atoms2)
if None in [closest_atom1, closest_atom2]:
print('Warning: Side chain interaction failed for %s and %s'%(group1.label, group2.label))
return None
# get the parameters
[dpka_max, cutoff] = version.get_hydrogen_bond_parameters(closest_atom1,closest_atom2)
if dpka_max==None or None in cutoff:
return None
# check that the closest atoms are close enough
if distance >= cutoff[1]:
return None
# check that bond distance criteria is met
bond_distance_too_short = group1.atom.is_atom_within_bond_distance(group2.atom,
version.parameters.min_bond_distance_for_hydrogen_bonds,1)
if bond_distance_too_short:
return None
# set the angle factor
#
# ---closest_atom1/2
# .
# .
# the_hydrogen---closest_atom2/1---
f_angle = 1.0
if group2.type in version.parameters.angular_dependent_sidechain_interactions:
if closest_atom2.element == 'H':
heavy_atom = closest_atom2.bonded_atoms[0]
hydrogen = closest_atom2
distance, f_angle, nada = Source.calculations.AngleFactorX(closest_atom1, hydrogen, heavy_atom)
else:
# Either the structure is corrupt (no hydrogen), or the heavy atom is closer to
# the titratable atom than the hydrogen. In either case we set the angle factor
# to 0
f_angle = 0.0
elif group1.type in version.parameters.angular_dependent_sidechain_interactions:
if closest_atom1.element == 'H':
heavy_atom = closest_atom1.bonded_atoms[0]
hydrogen = closest_atom1
distance, f_angle, nada = Source.calculations.AngleFactorX(closest_atom2, hydrogen, heavy_atom)
else:
# Either the structure is corrupt (no hydrogen), or the heavy atom is closer to
# the titratable atom than the hydrogen. In either case we set the angle factor
# to 0
f_angle = 0.0
weight = version.calculatePairWeight(group1.Nmass, group2.Nmass)
exception, value = version.checkExceptions(group1, group2)
#exception = False # circumventing exception
if exception == True:
""" do nothing, value should have been assigned """
#print(" exception for %s %s %6.2lf" % (group1.label, group2.label, value))
else:
value = version.calculateSideChainEnergy(distance, dpka_max, cutoff, weight, f_angle)
# print('distance',distance)
# print('dpka_max',dpka_max)
# print('cutoff',cutoff)
# print('f_angle',f_angle)
# print('weight',weight)
# print('value',value)
# print('===============================================')
return value
def HydrogenBondEnergy(distance, dpka_max, cutoff, f_angle=1.0):
"""
returns a hydrogen-bond interaction pKa shift
"""
if distance < cutoff[0]:
value = 1.00
elif distance > cutoff[1]:
value = 0.00
else:
value = 1.0-(distance-cutoff[0])/(cutoff[1]-cutoff[0])
dpKa = dpka_max*value*f_angle
return abs(dpKa)
def electrostatic_interaction(group1, group2, distance, version):
# check if we should do coulomb interaction at all
if not version.checkCoulombPair(group1, group2, distance):
return None
weight = version.calculatePairWeight(group1.Nmass, group2.Nmass)
value = version.calculateCoulombEnergy(distance, weight)
return value
def checkCoulombPair(parameters, group1, group2, distance):
"""
Checks if this Coulomb interaction should be done - a propka2.0 hack
"""
Npair = group1.Nmass + group2.Nmass
do_coulomb = True
# check if both groups are titratable (ions are taken care of in determinants::setIonDeterminants)
if not (group1.titratable and group2.titratable):
do_coulomb = False
# check if the distance is not too big
if distance > parameters.coulomb_cutoff2:
do_coulomb = False
# check that desolvation is ok
if Npair < parameters.Nmin:
do_coulomb = False
return do_coulomb
def CoulombEnergy(distance, weight, parameters):
"""
calculates the Coulomb interaction pKa shift based on Coulombs law
eps = 60.0 for the moment; to be scaled with 'weight'
"""
#diel = 80.0 - 60.0*weight
diel = 160 - (160 -30)*weight
R = max(distance, parameters.coulomb_cutoff1)
scale = (R - parameters.coulomb_cutoff2)/(parameters.coulomb_cutoff1 -parameters.coulomb_cutoff2)
scale = max(0.0, scale)
scale = min(1.0, scale)
dpka = 244.12/(diel*R) *scale
return abs(dpka)
def BackBoneReorganization(parameters, conformation):
"""
adding test stuff
"""
titratable_groups = conformation.get_backbone_reorganisation_groups()
BBC_groups = conformation.get_backbone_CO_groups()
for titratable_group in titratable_groups:
weight = titratable_group.buried
dpKa = 0.00
for BBC_group in BBC_groups:
center = [titratable_group.x, titratable_group.y, titratable_group.z]
distance, f_angle, nada = AngleFactorX(atom2=BBC_group.get_interaction_atoms(titratable_group)[0],
atom3=BBC_group.atom,
center=center)
if distance < 6.0 and f_angle > 0.001:
value = 1.0-(distance-3.0)/(6.0-3.0)
dpKa += 0.80*min(1.0, value)
titratable_group.Elocl = dpKa*weight
return
#
# Exception methods
#
def checkExceptions(version, group1, group2):
"""
checks for exceptions for this version - using defaults
"""
resType1 = group1.type
resType2 = group2.type
if (resType1 == "COO" and resType2 == "ARG"):
exception, value = checkCooArgException(group1, group2, version)
elif (resType1 == "ARG" and resType2 == "COO"):
exception, value = checkCooArgException(group2, group1, version)
elif (resType1 == "COO" and resType2 == "COO"):
exception, value = checkCooCooException(group1, group2, version)
elif (resType1 == "CYS" and resType2 == "CYS"):
exception, value = checkCysCysException(group1, group2, version)
elif (resType1 == "COO" and resType2 == "HIS") or \
(resType1 == "HIS" and resType2 == "COO"):
exception, value = checkCooHisException(group1, group2, version)
elif (resType1 == "OCO" and resType2 == "HIS") or \
(resType1 == "HIS" and resType2 == "OCO"):
exception, value = checkOcoHisException(group1, group2, version)
elif (resType1 == "CYS" and resType2 == "HIS") or \
(resType1 == "HIS" and resType2 == "CYS"):
exception, value = checkCysHisException(group1, group2, version)
else:
# do nothing, no exception for this pair
exception = False; value = None
return exception, value
def checkCooArgException(group_coo, group_arg, version):
"""
checking Coo-Arg exception: uses the two shortes unique distances (involving 2+2 atoms)
"""
str = "xxx"
exception = True
value_tot = 0.00
#dpka_max = parameters.sidechain_interaction.get_value(group_coo.type,group_arg.type)
#cutoff = parameters.sidechain_cutoffs.get_value(group_coo.type,group_arg.type)
# needs to be this way since you want to find shortest distance first
#print("--- exception for %s %s ---" % (group_coo.label, group_arg.label))
atoms_coo = []
atoms_coo.extend(group_coo.get_interaction_atoms(group_arg))
atoms_arg = []
atoms_arg.extend(group_arg.get_interaction_atoms(group_coo))
for iter in ["shortest", "runner-up"]:
# find the closest interaction pair
[closest_coo_atom, distance, closest_arg_atom] = get_smallest_distance(atoms_coo, atoms_arg)
[dpka_max, cutoff] = version.get_hydrogen_bond_parameters(closest_coo_atom,closest_arg_atom)
# calculate and sum up interaction energy
f_angle = 1.00
if group_arg.type in version.parameters.angular_dependent_sidechain_interactions:
atom3 = closest_arg_atom.bonded_atoms[0]
distance, f_angle, nada = AngleFactorX(closest_coo_atom, closest_arg_atom, atom3)
value = HydrogenBondEnergy(distance, dpka_max, cutoff, f_angle)
#print(iter, closest_coo_atom, closest_arg_atom,distance,value)
value_tot += value
# remove closest atoms before we attemp to find the runner-up pair
atoms_coo.remove(closest_coo_atom)
atoms_arg.remove(closest_arg_atom)
return exception, value_tot
def checkCooCooException(group1, group2, version):
"""
checking Coo-Coo hydrogen-bond exception
"""
exception = True
[closest_atom1, distance, closest_atom2] = get_smallest_distance(group1.get_interaction_atoms(group2),
group2.get_interaction_atoms(group1))
#dpka_max = parameters.sidechain_interaction.get_value(group1.type,group2.type)
#cutoff = parameters.sidechain_cutoffs.get_value(group1.type,group2.type)
[dpka_max, cutoff] = version.get_hydrogen_bond_parameters(closest_atom1,closest_atom2)
f_angle = 1.00
value = HydrogenBondEnergy(distance, dpka_max, cutoff, f_angle)
weight = calculatePairWeight(version.parameters, group1.Nmass, group2.Nmass)
value = value * (1.0 + weight)
return exception, value
def checkCooHisException(group1, group2, version):
"""
checking Coo-His exception
"""
exception = False
if checkBuried(group1.Nmass, group2.Nmass):
exception = True
return exception, version.parameters.COO_HIS_exception
def checkOcoHisException(group1, group2, version):
"""
checking Coo-His exception
"""
exception = False
if checkBuried(group1.Nmass, group2.Nmass):
exception = True
return exception, version.parameters.OCO_HIS_exception
def checkCysHisException(group1, group2, version):
"""
checking Cys-His exception
"""
exception = False
if checkBuried(group1.Nmass, group2.Nmass):
exception = True
return exception, version.parameters.CYS_HIS_exception
def checkCysCysException(group1, group2, version):
"""
checking Cys-Cys exception
"""
exception = False
if checkBuried(group1.Nmass, group2.Nmass):
exception = True
return exception, version.parameters.CYS_CYS_exception
def checkBuried(Nmass1, Nmass2):
"""
returns True if an interaction is buried
"""
if (Nmass1 + Nmass2 <= 900) and (Nmass1 <= 400 or Nmass2 <= 400):
return False
else:
return True

View File

@@ -0,0 +1,439 @@
#
# Container for molecular conformations
#
import Source.group, Source.determinants, Source.determinant, Source.ligand, Source.output, Source.coupled_groups, functools
class Conformation_container:
def __init__(self, name='', parameters=None, molecular_container=None):
self.molecular_container = molecular_container
self.name=name
self.parameters=parameters
self.atoms = []
self.groups = []
self.chains = []
self.current_iter_item = 0
self.marvin_pkas_calculated = False
self.non_covalently_coupled_groups = False
return
#
# Group related methods
#
def extract_groups(self):
""" Generates at list of molecular groups needed for calculating pKa values """
for atom in self.get_non_hydrogen_atoms():
# has this atom been checked for groups?
if atom.groups_extracted == 0:
self.validate_group(Source.group.is_group(self.parameters, atom))
# if the atom has been checked in a another conformation, check if it has a
# group that should be used in this conformation as well
elif atom.group:
self.validate_group(atom.group)
return
def additional_setup_when_reading_input_file(self):
# if a group is coupled and we are reading a .propka_input file,
# some more configuration might be needed
# print coupling map
map = Source.output.make_interaction_map('Covalent coupling map for %s'%self,
self.get_covalently_coupled_groups(),
lambda g1,g2: g1 in g2.covalently_coupled_groups)
print(map)
# check if we should set a common charge centre as well
if self.parameters.common_charge_centre:
self.set_common_charge_centres()
return
def set_common_charge_centres(self):
for system in self.get_coupled_systems(self.get_covalently_coupled_groups(), Source.group.Group.get_covalently_coupled_groups):
# make a list of the charge centre coordinates
all_coordinates = list(map(lambda g: [g.x, g.y, g.z], system))
# find the common charge center
ccc = functools.reduce(lambda g1,g2: [g1[0]+g2[0], g1[1]+g2[1], g1[2]+g2[2]], all_coordinates)
ccc = list(map(lambda c: c/len(system), ccc))
# set the ccc for all coupled groups in this system
for g in system:
[g.x, g.y, g.z] = ccc
g.common_charge_centre = True
return
def find_covalently_coupled_groups(self):
""" Finds covalently coupled groups and sets common charge centres if needed """
for group in [ group for group in self.groups if group.titratable]:
# Find covalently bonded groups
bonded_groups = self.find_bonded_titratable_groups(group.atom, 1, group.atom)
# couple groups
for cg in bonded_groups:
if cg in group.covalently_coupled_groups:
continue
if cg.atom.sybyl_type == group.atom.sybyl_type:
group.couple_covalently(cg)
# check if we should set a common charge centre as well
if self.parameters.common_charge_centre:
self.set_common_charge_centres()
# print coupling map
map = Source.output.make_interaction_map('Covalent coupling map for %s'%self,
#self.get_titratable_groups(),
self.get_covalently_coupled_groups(),
lambda g1,g2: g1 in g2.covalently_coupled_groups)
print(map)
return
def find_non_covalently_coupled_groups(self, verbose=False):
# check if non-covalent coupling has already been set up in an input file
if len(list(filter(lambda g: len(g.non_covalently_coupled_groups)>0, self.get_titratable_groups())))>0:
self.non_covalently_coupled_groups = True
Source.coupled_groups.nccg.identify_non_covalently_coupled_groups(self,verbose=verbose)
# re-do the check
if len(list(filter(lambda g: len(g.non_covalently_coupled_groups)>0, self.get_titratable_groups())))>0:
self.non_covalently_coupled_groups = True
return
def find_bonded_titratable_groups(self, atom, no_bonds, original_atom):
res = set()
for ba in atom.bonded_atoms:
# skip the original atom
if ba == original_atom:
continue
# check if this atom has a titratable group
if ba.group and ba.group.titratable and no_bonds <= self.parameters.coupling_max_number_of_bonds:
res.add(ba.group)
# check for titratable groups bonded to this atom
if no_bonds < self.parameters.coupling_max_number_of_bonds:
res |= self.find_bonded_titratable_groups(ba,no_bonds+1, original_atom)
return res
def validate_group(self, group):
""" Checks if we want to include this group in the calculations """
# Is it recognized as a group at all?
if not group:
return
# Other checks (include ligands, which chains etc.)
# if all ok, accept the group
self.accept_group(group)
return
def accept_group(self, group):
# set up the group
group.parameters=self.parameters
group.setup()
# and store it
self.groups.append(group)
return
#
# pka calculation methods
#
def calculate_pka(self, version, options):
print('\nCalculating pKas for',self)
# calculate desolvation
for group in self.get_titratable_groups()+self.get_ions():
version.calculate_desolvation(group)
# calculate backbone interactions
Source.determinants.setBackBoneDeterminants(self.get_titratable_groups(), self.get_backbone_groups(), version)
# setting ion determinants
Source.determinants.setIonDeterminants(self, version)
# calculating the back-bone reorganization/desolvation term
version.calculateBackBoneReorganization(self)
# setting remaining non-iterative and iterative side-chain & Coulomb interaction determinants
Source.determinants.setDeterminants(self.get_sidechain_groups(), version=version, options=options)
# calculating the total pKa values
for group in self.groups: group.calculate_total_pka()
# take coupling effects into account
penalised_labels = self.coupling_effects()
if self.parameters.remove_penalised_group and len(penalised_labels)>0:
print('Removing penalised groups!!!')
for g in self.get_titratable_groups():
g.remove_determinants(penalised_labels)
# re-calculating the total pKa values
for group in self.groups: group.calculate_total_pka()
return
def coupling_effects(self):
#
# Bases: The group with the highest pKa (the most stable one in the
# charged form) will be the first one to adopt a proton as pH
# is lowered and this group is allowed to titrate.
# The remaining groups are penalised
#
# Acids: The group with the highest pKa (the least stable one in the
# charged form) will be the last group to loose the proton as
# pH is raised and will be penalised.
# The remaining groups are allowed to titrate.
#
penalised_labels = []
for all_groups in self.get_coupled_systems(self.get_covalently_coupled_groups(),
Source.group.Group.get_covalently_coupled_groups):
# check if we should share determinants
if self.parameters.shared_determinants:
self.share_determinants(all_groups)
# find the group that has the highest pKa value
first_group = max(all_groups, key=lambda g:g.pka_value)
# In case of acids
if first_group.charge < 0:
first_group.coupled_titrating_group = min(all_groups, key=lambda g:g.pka_value)
penalised_labels.append(first_group.label) # group with the highest pKa is penalised
# In case of bases
else:
for a in all_groups:
if a == first_group:
continue # group with the highest pKa is allowed to titrate...
a.coupled_titrating_group = first_group
penalised_labels.append(a.label) #... and the rest is penalised
return penalised_labels
def share_determinants(self, groups):
# make a list of the determinants to share
types = ['sidechain','backbone','coulomb']
for type in types:
# find maximum value for each determinant
max_dets = {}
for g in groups:
for d in g.determinants[type]:
# update max dets
if d.group not in max_dets.keys():
max_dets[d.group] = d.value
else:
max_dets[d.group] = max(d.value, max_dets[d.group], key= lambda v: abs(v))
# overwrite/add maximum value for each determinant
for det_group in max_dets.keys():
new_determinant = Source.determinant.Determinant(det_group, max_dets[det_group])
for g in groups:
g.set_determinant(new_determinant,type)
return
def get_coupled_systems(self, groups, get_coupled_groups):
""" This generator will yield one covalently coupled system at the time """
groups = set(groups)
while len(groups)>0:
# extract a system of coupled groups ...
system = set()
self.get_a_coupled_system_of_groups(groups.pop(), system, get_coupled_groups)
# ... and remove them from the list
groups -= system
yield system
return
def get_a_coupled_system_of_groups(self, new_group, coupled_groups, get_coupled_groups):
coupled_groups.add(new_group)
[self.get_a_coupled_system_of_groups(c, coupled_groups, get_coupled_groups) for c in get_coupled_groups(new_group) if c not in coupled_groups]
return
#
# Energy/summary-related methods
#
def calculate_folding_energy(self, pH=None, reference=None):
ddg = 0.0
for group in self.groups:
#print('Folding energy for %s at pH %f: %f'%(group,pH,group.calculate_folding_energy(self.parameters, pH=pH, reference=reference)))
ddg += group.calculate_folding_energy(self.parameters, pH=pH, reference=reference)
return ddg
def calculate_charge(self, parmaeters, pH=None):
unfolded = folded = 0.0
for group in self.get_titratable_groups():
unfolded += group.calculate_charge(parmaeters, pH=pH, state='unfolded')
folded += group.calculate_charge(parmaeters, pH=pH, state='folded')
return unfolded,folded
#
# conformation/bookkeeping/atom methods
#
def get_backbone_groups(self):
""" returns all backbone groups needed for the pKa calculations """
return [group for group in self.groups if 'BB' in group.type]
def get_sidechain_groups(self):
""" returns all sidechain groups needed for the pKa calculations """
return [group for group in self.groups if ('BB' not in group.type\
and not group.atom.cysteine_bridge)]
def get_covalently_coupled_groups(self):
return [g for g in self.groups if len(g.covalently_coupled_groups)>0]
def get_non_covalently_coupled_groups(self):
return [g for g in self.groups if len(g.non_covalently_coupled_groups)>0]
def get_backbone_NH_groups(self):
""" returns all NH backbone groups needed for the pKa calculations """
return [group for group in self.groups if group.type == 'BBN']
def get_backbone_CO_groups(self):
""" returns all CO backbone groups needed for the pKa calculations """
return [group for group in self.groups if group.type == 'BBC']
def get_groups_in_residue(self, residue):
return [group for group in self.groups if group.residue_type == residue]
def get_titratable_groups(self):
return [group for group in self.groups if group.titratable]
def get_titratable_groups_and_cysteine_bridges(self):
return [group for group in self.groups if group.titratable or group.residue_type == 'CYS']
def get_acids(self):
return [group for group in self.groups if (group.residue_type in self.parameters.acid_list
and not group.atom.cysteine_bridge)]
def get_backbone_reorganisation_groups(self):
return [group for group in self.groups if (group.residue_type in self.parameters.backbone_reorganisation_list
and not group.atom.cysteine_bridge)]
def get_ions(self):
return [group for group in self.groups if group.residue_type in self.parameters.ions.keys()]
def get_group_names(self, list):
return [group for group in self.groups if group.type in list]
def get_ligand_atoms(self):
return [atom for atom in self.atoms if atom.type=='hetatm']
def get_heavy_ligand_atoms(self):
return [atom for atom in self.atoms if atom.type=='hetatm' and atom.element != 'H']
def get_chain(self,chain):
return [atom for atom in self.atoms if atom.chainID != chain]
def add_atom(self, atom):
#print(self,'adding',atom)
self.atoms.append(atom)
if not atom.conformation_container:
atom.conformation_container = self
if not atom.molecular_container:
atom.molecular_container = self.molecular_container
# store chain id for bookkeeping
if not atom.chainID in self.chains:
self.chains.append(atom.chainID)
return
def copy_atom(self, atom):
new_atom = atom.makeCopy()
self.atoms.append(new_atom)
new_atom.conformation_container = self
return
def get_non_hydrogen_atoms(self):
return [atom for atom in self.atoms if atom.element!='H']
def top_up(self, other):
""" Tops up self with all atoms found in other but not in self """
for atom in other.atoms:
if not self.have_atom(atom):
self.copy_atom(atom)
return
def have_atom(self, atom):
res = False
for a in self.atoms:
if a.residue_label == atom.residue_label:
res = True
break
return res
def find_group(self, group):
for g in self.groups:
if g.atom.residue_label == group.atom.residue_label:
if g.type == group.type:
return g
return False
def set_ligand_atom_names(self):
for atom in self.get_ligand_atoms():
Source.ligand.assign_sybyl_type(atom)
return
def __str__(self):
return'Conformation container %s with %d atoms and %d groups'%(self.name,len(self),len(self.groups))
def __len__(self):
return len(self.atoms)
def sort_atoms(self):
# sort the atoms ...
self.atoms.sort(key=self.sort_atoms_key)
# ... and re-number them
for i in range(len(self.atoms)):
self.atoms[i].numb = i+1
return
def sort_atoms_key(self, atom):
key = ord(atom.chainID)*1e7
key += atom.resNumb*1000
if len(atom.name) > len(atom.element):
key += ord(atom.name[len(atom.element)])
#print(atom,ord(atom.name[len(atom.element)]), '|%s||%s|'%(atom.name,atom.element))
return key

318
Source/coupled_groups.py Normal file
View File

@@ -0,0 +1,318 @@
import math, Source.output, Source.group, Source.lib, itertools
class non_covalently_couple_groups:
def __init__(self):
self.parameters = None
# self.do_intrinsic = False
# self.do_pair_wise = False
self.do_prot_stat = True
return
#
# Methods for finding coupled groups
#
def is_coupled_protonation_state_probability(self, group1, group2, energy_method, return_on_fail=True):
# check if the interaction energy is high enough
interaction_energy = max(self.get_interaction(group1,group2), self.get_interaction(group2,group1))
if interaction_energy<=self.parameters.min_interaction_energy and return_on_fail:
return {'coupling_factor':-1.0}
# calculate intrinsic pKa's, if not already done
for group in [group1, group2]:
if not hasattr(group, 'intrinsic_pKa'):
group.calculate_intrinsic_pka()
use_pH = self.parameters.pH
if self.parameters.pH == 'variable':
use_pH = min(group1.pka_value, group2.pka_value)
default_energy = energy_method(pH=use_pH, reference=self.parameters.reference)
default_pka1 = group1.pka_value
default_pka2 = group2.pka_value
# check that pka values are within relevant limits
if max(default_pka1, default_pka2) < self.parameters.min_pka or \
min(default_pka1, default_pka2) > self.parameters.max_pka:
if return_on_fail:
return {'coupling_factor':-1.0}
# Swap interactions and re-calculate pKa values
self.swap_interactions([group1], [group2])
group1.calculate_total_pka()
group2.calculate_total_pka()
# store swapped energy and pka's
swapped_energy = energy_method(pH=use_pH, reference=self.parameters.reference)
swapped_pka1 = group1.pka_value
swapped_pka2 = group2.pka_value
pka_shift1 = swapped_pka1 - default_pka1
pka_shift2 = swapped_pka2 - default_pka2
# Swap back to original protonation state
self.swap_interactions([group1], [group2])
group1.calculate_total_pka()
group2.calculate_total_pka()
# check difference in free energy
if abs(default_energy - swapped_energy) > self.parameters.max_free_energy_diff and return_on_fail:
return {'coupling_factor':-1.0}
# check pka shift
if max(abs(pka_shift1), abs(pka_shift2)) < self.parameters.min_swap_pka_shift and return_on_fail:
return {'coupling_factor':-1.0}
# check intrinsic pka diff
if abs(group1.intrinsic_pKa - group2.intrinsic_pKa) > self.parameters.max_intrinsic_pKa_diff and return_on_fail:
return {'coupling_factor':-1.0}
# if everything is OK, calculate the coupling factor and return all info
factor = self.get_free_energy_diff_factor(default_energy, swapped_energy)*\
self.get_pka_diff_factor(group1.intrinsic_pKa, group2.intrinsic_pKa)*\
self.get_interaction_factor(interaction_energy)
return {'coupling_factor':factor,
'default_energy':default_energy,
'swapped_energy':swapped_energy,
'interaction_energy':interaction_energy,
'swapped_pka1':swapped_pka1,
'swapped_pka2':swapped_pka2,
'pka_shift1':pka_shift1,
'pka_shift2':pka_shift2,
'pH':use_pH}
#
# Methods for calculating the coupling factor
#
def get_pka_diff_factor(self, pka1, pka2):
intrinsic_pka_diff = abs(pka1-pka2)
res = 0.0
if intrinsic_pka_diff <= self.parameters.max_intrinsic_pKa_diff:
res = 1-(intrinsic_pka_diff/self.parameters.max_intrinsic_pKa_diff)**2
return res
def get_free_energy_diff_factor(self, energy1, energy2):
free_energy_diff = abs(energy1-energy2)
res = 0.0
if free_energy_diff <= self.parameters.max_free_energy_diff:
res = 1-(free_energy_diff/self.parameters.max_free_energy_diff)**2
return res
def get_interaction_factor(self, interaction_energy):
res = 0.0
interaction_energy = abs(interaction_energy)
if interaction_energy >= self.parameters.min_interaction_energy:
res = (interaction_energy-self.parameters.min_interaction_energy)/(1.0+interaction_energy-self.parameters.min_interaction_energy)
return res
def identify_non_covalently_coupled_groups(self, conformation, verbose=True):
""" Finds coupled residues in protein """
self.parameters = conformation.parameters
if verbose:
print('')
print(' Warning: When using the -d option, pKa values based on \'swapped\' interactions')
print(' will be writting to the output .pka file')
print('')
print('-'*103)
print(' Detecting non-covalently coupled residues')
print('-'*103)
print(' Maximum pKa difference: %4.2f pKa units'%self.parameters.max_intrinsic_pKa_diff)
print(' Minimum interaction energy: %4.2f pKa units'%self.parameters.min_interaction_energy)
print(' Maximum free energy diff.: %4.2f pKa units'%self.parameters.max_free_energy_diff)
print(' Minimum swap pKa shift: %4.2f pKa units'%self.parameters.min_swap_pka_shift)
print(' pH: %6s ' %str(self.parameters.pH))
print(' Reference: %s' %self.parameters.reference)
print(' Min pKa: %4.2f'%self.parameters.min_pka)
print(' Max pKa: %4.2f'%self.parameters.max_pka)
print('')
# find coupled residues
titratable_groups = conformation.get_titratable_groups()
if not conformation.non_covalently_coupled_groups:
for g1 in titratable_groups:
for g2 in titratable_groups:
if g1==g2:
break
if not g1 in g2.non_covalently_coupled_groups and self.do_prot_stat:
data = self.is_coupled_protonation_state_probability(g1, g2,conformation.calculate_folding_energy)
if data['coupling_factor'] >0.0:
g1.couple_non_covalently(g2)
if verbose:
self.print_out_swaps(conformation)
return
def print_out_swaps(self, conformation, verbose=True):
map = Source.output.make_interaction_map('Non-covalent coupling map for %s'%conformation,
conformation.get_non_covalently_coupled_groups(),
lambda g1,g2: g1 in g2.non_covalently_coupled_groups)
print(map)
for system in conformation.get_coupled_systems(conformation.get_non_covalently_coupled_groups(),Source.group.Group.get_non_covalently_coupled_groups):
self.print_system(conformation, list(system))
return
def print_system(self, conformation, system):
print('System containing %d groups:'%len(system))
# make list of interactions withi this system
interactions = list(itertools.combinations(system,2))
# print out coupling info for each interaction
coup_info = ''
for interaction in interactions:
data = self.is_coupled_protonation_state_probability(interaction[0], interaction[1],conformation.calculate_folding_energy, return_on_fail=False)
coup_info += self.make_data_to_string(data,interaction[0],interaction[1])+'\n\n'
print(coup_info)
# make list of possible combinations of swap to try out
combinations = Source.lib.generate_combinations(interactions)
# Make possible swap combinations
swap_info = ''
swap_info += self.print_determinants_section(system,'Original')
for combination in combinations:
# Tell the user what is swap in this combination
swap_info += 'Swapping the following interactions:\n'
for interaction in combination:
swap_info += ' %s %s\n'%(interaction[0].label, interaction[1].label)
# swap...
for interaction in combination:
self.swap_interactions([interaction[0]],[interaction[1]])
swap_info += self.print_determinants_section(system,'Swapped')
# ...and swap back
#for interaction in combination:
# self.swap_interactions([interaction[0]], [interaction[1]])
print(swap_info)
return
#
# Interaction and swapping methods
#
def get_interaction(self, group1, group2, include_side_chain_hbs = True):
determinants = group1.determinants['coulomb']
if include_side_chain_hbs:
determinants = group1.determinants['sidechain'] + group1.determinants['coulomb']
interaction_energy = 0.0
for det in determinants:
if group2 == det.group:
interaction_energy += det.value
return interaction_energy
def print_determinants_section(self, system, tag):
all_labels = [g.label for g in system]
s = ' '+'-'*113+'\n'
for group in system:
s += self.tagged_print(' %-8s|'%tag,group.getDeterminantString(), all_labels)
return s+'\n'
def swap_interactions(self, groups1, groups2, include_side_chain_hbs = True):
for i in range(len(groups1)):
group1 = groups1[i]
group2 = groups2[i]
# swap the interactions!
self.transfer_determinant(group1.determinants['coulomb'], group2.determinants['coulomb'], group1.label, group2.label)
if include_side_chain_hbs:
self.transfer_determinant(group1.determinants['sidechain'], group2.determinants['sidechain'], group1.label, group2.label)
# re-calculate pKa values
group1.calculate_total_pka()
group2.calculate_total_pka()
return
def transfer_determinant(self, determinants1, determinants2, label1, label2):
# find out what to transfer...
from1to2 = []
from2to1 = []
for det in determinants1:
if det.label == label2:
from1to2.append(det)
for det in determinants2:
if det.label == label1:
from2to1.append(det)
# ...and transfer it!
for det in from1to2:
det.label = label1
determinants2.append(det)
determinants1.remove(det)
for det in from2to1:
det.label = label2
determinants1.append(det)
determinants2.remove(det)
return
#
# Output methods
#
def tagged_print(self, tag, s, labels):
s = "%s %s"%(tag,s)
s = s.replace('\n','\n%s '%tag)
for label in labels:
s = s.replace(label, '\033[31m%s\033[30m'%label)
return s+'\n'
def make_data_to_string(self, data, group1, group2):
s = """ %s and %s coupled (prot.state): %5.2f
Energy levels: %6.2f, %6.2f (difference: %6.2f) at pH %6.2f
Interaction energy: %6.2f
Intrinsic pka's: %6.2f, %6.2f (difference: %6.2f)
Swapped pKa's: %6.2f, %6.2f (difference: %6.2f, %6.2f)"""%(group1.label,
group2.label,
data['coupling_factor'],
data['default_energy'], data['swapped_energy'],
data['default_energy'] - data['swapped_energy'],
data['pH'],
data['interaction_energy'],
group1.intrinsic_pKa,
group2.intrinsic_pKa,
group1.intrinsic_pKa-group2.intrinsic_pKa,
data['swapped_pka1'],
data['swapped_pka2'],
data['pka_shift1'],
data['pka_shift2'])
return s
nccg = non_covalently_couple_groups()

26
Source/determinant.py Normal file
View File

@@ -0,0 +1,26 @@
class Determinant:
"""
Determinant class - set up for later structurization
"""
def __init__(self, group, value):
"""
Contructer of determinant object - simple, but helps in creating structure!
"""
self.group = group
self.label = group.label
self.value = value
return
def add(self, value):
"""
adding a value to determinant
"""
self.value += value
return
def __str__(self):
return '%s: %8.2f'%(self.label,self.value)

238
Source/determinants.py Normal file
View File

@@ -0,0 +1,238 @@
import math, time
import Source.iterative, Source.lib, Source.vector_algebra
import Source.calculations
from Source.determinant import Determinant
def setDeterminants(propka_groups, version=None, options=None):
"""
adding side-chain and coulomb determinants/perturbations to all residues - note, backbone determinants are set separately
"""
iterative_interactions = []
# --- NonIterative section ---#
for group1 in propka_groups:
for group2 in propka_groups:
if group1 == group2:
break
# do not calculate interactions for coupled groups
if group2 in group1.covalently_coupled_groups:
break
distance = Source.calculations.distance(group1, group2)
if distance < version.parameters.coulomb_cutoff2:
interaction_type = version.parameters.interaction_matrix.get_value(group1.type,group2.type)
if interaction_type == 'I':
Source.iterative.addtoDeterminantList(group1, group2, distance, iterative_interactions, version=version)
elif interaction_type == 'N':
addDeterminants(group1, group2, distance, version)
# --- Iterative section ---#
Source.iterative.addDeterminants(iterative_interactions, version, options=options)
def addDeterminants(group1, group2, distance, version):
"""
adding determinants/perturbations, distance(R1, R2) < coulomb_cutoff always
"""
# side-chain determinant
addSidechainDeterminants(group1, group2, version)
# Coulomb determinant
addCoulombDeterminants(group1, group2, distance, version)
return
def addSidechainDeterminants(group1, group2, version=None):
"""
adding side-chain determinants/perturbations
Note, resNumb1 > resNumb2
"""
hbond_interaction = version.hydrogen_bond_interaction(group1, group2)
if hbond_interaction:
if group1.charge == group2.charge:
# acid pair or base pair
if group1.model_pka < group2.model_pka:
newDeterminant1 = Determinant(group2, -hbond_interaction)
newDeterminant2 = Determinant(group1, hbond_interaction)
else:
newDeterminant1 = Determinant(group2, hbond_interaction)
newDeterminant2 = Determinant(group1, -hbond_interaction)
else:
newDeterminant1 = Determinant(group2, hbond_interaction*group1.charge)
newDeterminant2 = Determinant(group1, hbond_interaction*group2.charge)
group1.determinants['sidechain'].append(newDeterminant1)
group2.determinants['sidechain'].append(newDeterminant2)
return
def addCoulombDeterminants(group1, group2, distance, version):
"""
adding NonIterative Coulomb determinants/perturbations
"""
coulomb_interaction = version.electrostatic_interaction(group1, group2, distance)
if coulomb_interaction:
Q1 = group1.charge
Q2 = group2.charge
# assigning the Coulombic interaction
if Q1 < 0.0 and Q2 < 0.0:
""" both are acids """
addCoulombAcidPair(group1, group2, coulomb_interaction)
elif Q1 > 0.0 and Q2 > 0.0:
""" both are bases """
addCoulombBasePair(group1, group2, coulomb_interaction)
else:
""" one of each """
addCoulombIonPair(group1, group2, coulomb_interaction)
return
def addCoulombAcidPair(object1, object2, value):
"""
Adding the Coulomb interaction (an acid pair):
the higher pKa is raised
"""
if object1.model_pka > object2.model_pka:
newDeterminant = Determinant(object2, value)
object1.determinants['coulomb'].append(newDeterminant)
else:
newDeterminant = Determinant(object1, value)
object2.determinants['coulomb'].append(newDeterminant)
def addCoulombBasePair(object1, object2, value):
"""
Adding the Coulomb interaction (a base pair):
the lower pKa is lowered
"""
if object1.model_pka < object2.model_pka:
newDeterminant = Determinant(object2, -value)
object1.determinants['coulomb'].append(newDeterminant)
else:
newDeterminant = Determinant(object1, -value)
object2.determinants['coulomb'].append(newDeterminant)
def addCoulombIonPair(object1, object2, value):
"""
Adding the Coulomb interaction (an acid-base pair):
the pKa of the acid is lowered & the pKa of the base is raised
"""
# residue1
Q1 = object1.charge
newDeterminant = Determinant(object2, Q1*value)
object1.determinants['coulomb'].append(newDeterminant)
# residue2
Q2 = object2.charge
newDeterminant = Determinant(object1, Q2*value)
object2.determinants['coulomb'].append(newDeterminant)
def setIonDeterminants(conformation_container, version):
"""
adding ion determinants/perturbations
"""
for titratable_group in conformation_container.get_titratable_groups():
for ion_group in conformation_container.get_ions():
squared_distance = Source.calculations.squared_distance(titratable_group, ion_group)
if squared_distance < version.parameters.coulomb_cutoff2_squared:
weight = version.calculatePairWeight(titratable_group.Nmass, ion_group.Nmass)
# the pKa of both acids and bases are shifted up by negative ions (and vice versa)
value = (-ion_group.charge) * version.calculateCoulombEnergy(math.sqrt(squared_distance), weight)
newDeterminant = Determinant(ion_group, value)
titratable_group.determinants['coulomb'].append(newDeterminant)
return
def setBackBoneDeterminants(titratable_groups, backbone_groups, version):
for titratable_group in titratable_groups:
# find out which backbone groups this titratable is interacting with
for backbone_group in backbone_groups:
# find the interacting atoms
backbone_interaction_atoms = backbone_group.get_interaction_atoms(titratable_group)
titratable_group_interaction_atoms = titratable_group.interaction_atoms_for_acids
# find the smallest distance
[backbone_atom, distance, titratable_atom] = Source.calculations.get_smallest_distance(backbone_interaction_atoms,
titratable_group_interaction_atoms)
# get the parameters
parameters = version.get_backbone_hydrogen_bond_parameters(backbone_atom, titratable_atom)
if not parameters:
continue
[dpKa_max, [cutoff1, cutoff2]] = parameters
if distance < cutoff2:
# calculate angle factor
f_angle = 1.0
# for BBC groups, the hydrogen is on the titratable group
#
# Titra.
# /
# H
# .
# O
# ||
# C
if backbone_group.type == 'BBC':
if titratable_group.type in version.parameters.angular_dependent_sidechain_interactions:
if titratable_atom.element == 'H':
heavy_atom = titratable_atom.bonded_atoms[0]
hydrogen_atom = titratable_atom
[d1, f_angle, d2] = Source.calculations.AngleFactorX(atom1=heavy_atom,
atom2=hydrogen_atom,
atom3=backbone_atom)
else:
# Either the structure is corrupt (no hydrogen), or the heavy atom is closer to
# the titratable atom than the hydrogen. In either case we set the angle factor
# to 0
f_angle = 0.0
# for BBN groups, the hydrogen is on the backbone group
#
# Titra.
# .
# H
# |
# N
# / \
if backbone_group.type == 'BBN':
if backbone_atom.element == 'H':
backbone_N = backbone_atom.bonded_atoms[0]
backbone_H = backbone_atom
[d1, f_angle, d2] = Source.calculations.AngleFactorX(atom1=titratable_atom,
atom2=backbone_H,
atom3=backbone_N)
else:
# Either the structure is corrupt (no hydrogen), or the heavy atom is closer to
# the titratable atom than the hydrogen. In either case we set the angle factor
# to 0
f_angle = 0.0
if f_angle > 0.001:
value = titratable_group.charge * Source.calculations.HydrogenBondEnergy(distance, dpKa_max, [cutoff1,cutoff2], f_angle)
newDeterminant = Determinant(backbone_group, value)
titratable_group.determinants['backbone'].append(newDeterminant)
return

1315
Source/group.py Normal file

File diff suppressed because it is too large Load Diff

358
Source/iterative.py Normal file
View File

@@ -0,0 +1,358 @@
import math, time
import Source.lib as lib
from Source.determinant import Determinant
import Source.calculations
# Some library functions for the interative pKa determinants
def addtoDeterminantList(group1, group2, distance, iterative_interactions, version):
"""
Adds 'iterative determinants' to list ..., [[R1, R2], [side-chain, coulomb], [A1, A2]], ...
Note, the sign is determined when the interaction is added to the iterative object!
Note, distance < coulomb_cutoff here
"""
hbond_value = version.hydrogen_bond_interaction(group1, group2)
coulomb_value = version.electrostatic_interaction(group1, group2, distance)
# adding the interaction to 'iterative_interactions'
if hbond_value or coulomb_value:
pair = [group1, group2]
values = [hbond_value, coulomb_value]
while None in values:
values[values.index(None)] = 0.0
annihilation = [0., 0.]
interaction = [pair, values, annihilation]
iterative_interactions.append(interaction)
return
def addIterativeAcidPair(object1, object2, interaction):
"""
Adding the Coulomb 'iterative' interaction (an acid pair):
the higher pKa is raised with QQ+HB
the lower pKa is lowered with HB
"""
values = interaction[1]
annihilation = interaction[2]
hbond_value = values[0]
coulomb_value = values[1]
diff = coulomb_value + 2*hbond_value
comp1 = object1.pKa_old + annihilation[0] + diff
comp2 = object2.pKa_old + annihilation[1] + diff
annihilation[0] = 0.
annihilation[1] = 0.
if comp1 > comp2:
# side-chain
determinant = [object2, hbond_value]
object1.determinants['sidechain'].append(determinant)
determinant = [object1, -hbond_value]
object2.determinants['sidechain'].append(determinant)
# Coulomb
determinant = [object2, coulomb_value]
object1.determinants['coulomb'].append(determinant)
annihilation[0] = -diff
else:
# side-chain
determinant = [object1, hbond_value]
object2.determinants['sidechain'].append(determinant)
determinant = [object2, -hbond_value]
object1.determinants['sidechain'].append(determinant)
# Coulomb
determinant = [object1, coulomb_value]
object2.determinants['coulomb'].append(determinant)
annihilation[1] = -diff
def addIterativeBasePair(object1, object2, interaction):
"""
Adding the Coulomb 'iterative' interaction (a base pair):
the lower pKa is lowered
"""
values = interaction[1]
annihilation = interaction[2]
hbond_value = values[0]
coulomb_value = values[1]
diff = coulomb_value + 2*hbond_value
diff = -diff
comp1 = object1.pKa_old + annihilation[0] + diff
comp2 = object2.pKa_old + annihilation[1] + diff
annihilation[0] = 0.
annihilation[1] = 0.
if comp1 < comp2:
# side-chain
determinant = [object2, -hbond_value]
object1.determinants['sidechain'].append(determinant)
determinant = [object1, hbond_value]
object2.determinants['sidechain'].append(determinant)
# Coulomb
determinant = [object2, -coulomb_value]
object1.determinants['coulomb'].append(determinant)
annihilation[0] = -diff
else:
# side-chain
determinant = [object1, -hbond_value]
object2.determinants['sidechain'].append(determinant)
determinant = [object2, hbond_value]
object1.determinants['sidechain'].append(determinant)
# Coulomb
determinant = [object1, -coulomb_value]
object2.determinants['coulomb'].append(determinant)
annihilation[1] = -diff
def addIterativeIonPair(object1, object2, interaction, version):
"""
Adding the Coulomb 'iterative' interaction (an acid-base pair):
the pKa of the acid is lowered & the pKa of the base is raised
"""
values = interaction[1]
annihilation = interaction[2]
hbond_value = values[0]
coulomb_value = values[1]
Q1 = object1.Q
Q2 = object2.Q
comp1 = object1.pKa_old + annihilation[0] + Q1*coulomb_value
comp2 = object2.pKa_old + annihilation[1] + Q2*coulomb_value
if object1.resName not in version.parameters.exclude_sidechain_interactions:
comp1 += Q1*hbond_value
if object2.resName not in version.parameters.exclude_sidechain_interactions:
comp2 += Q2*hbond_value
if Q1 == -1.0 and comp1 < comp2:
add_term = True # pKa(acid) < pKa(base)
elif Q1 == 1.0 and comp1 > comp2:
add_term = True # pKa(base) > pKa(acid)
else:
add_term = False
annihilation[0] = 0.00
annihilation[1] = 0.00
if add_term == True:
# Coulomb
if coulomb_value > 0.005:
# residue1
interaction = [object2, Q1*coulomb_value]
annihilation[0] += -Q1*coulomb_value
object1.determinants['coulomb'].append(interaction)
# residue2
interaction = [object1, Q2*coulomb_value]
annihilation[1] += -Q2*coulomb_value
object2.determinants['coulomb'].append(interaction)
# Side-chain
if hbond_value > 0.005:
# residue1
if object1.resName not in version.parameters.exclude_sidechain_interactions:
interaction = [object2, Q1*hbond_value]
annihilation[0] += -Q1*hbond_value
object1.determinants['sidechain'].append(interaction)
# residue2
if object2.resName not in version.parameters.exclude_sidechain_interactions:
interaction = [object1, Q2*hbond_value]
annihilation[1] += -Q2*hbond_value
object2.determinants['sidechain'].append(interaction)
def addDeterminants(iterative_interactions, version, options=None):
"""
The iterative pKa scheme. Later it is all added in 'calculateTotalPKA'
"""
# --- setup ---
iteratives = []
done_group = []
# creating iterative objects with references to their real group counterparts
for interaction in iterative_interactions:
pair = interaction[0]
for group in pair:
if group in done_group:
#print "done already"
""" do nothing - already have an iterative object for this group """
else:
newIterative = Iterative(group)
iteratives.append(newIterative)
done_group.append(group)
# Initialize iterative scheme
if options.verbose == True:
print("\n --- pKa iterations (%d groups, %d interactions) ---" % ( len(iteratives), len(iterative_interactions) ))
converged = False
iteration = 0
# set non-iterative pka values as first step
for itres in iteratives:
itres.pKa_iter.append(itres.pKa_NonIterative)
# --- starting pKa iterations ---
while converged == False:
# initialize pKa_new
iteration += 1
for itres in iteratives:
itres.determinants = {'sidechain':[],'backbone':[],'coulomb':[]}
itres.pKa_new = itres.pKa_NonIterative
# Adding interactions to temporary determinant container
for interaction in iterative_interactions:
pair = interaction[0]
values = interaction[1]
annihilation = interaction[2]
#print "len(interaction) = %d" % (len(interaction))
object1, object2 = findIterative(pair, iteratives)
Q1 = object1.Q
Q2 = object2.Q
if Q1 < 0.0 and Q2 < 0.0:
""" both are acids """
addIterativeAcidPair(object1, object2, interaction)
elif Q1 > 0.0 and Q2 > 0.0:
""" both are bases """
addIterativeBasePair(object1, object2, interaction)
else:
""" one of each """
addIterativeIonPair(object1, object2, interaction, version)
# Calculating pKa_new values
for itres in iteratives:
for type in ['sidechain','backbone','coulomb']:
for determinant in itres.determinants[type]:
itres.pKa_new += determinant[1]
# Check convergence
converged = True
for itres in iteratives:
if itres.pKa_new == itres.pKa_old:
itres.converged = True
else:
itres.converged = False
converged = False
# reset pKa_old & storing pKa_new in pKa_iter
for itres in iteratives:
itres.pKa_old = itres.pKa_new
itres.pKa_iter.append(itres.pKa_new)
if iteration == 10:
print("did not converge in %d iterations" % (iteration))
break
# --- Iterations finished ---
# printing pKa iterations
if options.verbose == True:
str = "%12s" % (" ")
for index in range(0, iteration+1 ):
str += "%8d" % (index)
print(str)
for itres in iteratives:
str = "%s " % (itres.label)
for pKa in itres.pKa_iter:
str += "%8.2lf" % (pKa)
if itres.converged == False:
str += " *"
print(str)
# creating real determinants and adding them to group object
for itres in iteratives:
for type in ['sidechain','backbone','coulomb']:
for interaction in itres.determinants[type]:
#print('done',itres.group.label,interaction[0],interaction[1])
value = interaction[1]
if value > 0.005 or value < -0.005:
g = interaction[0]
newDeterminant = Determinant(g, value)
itres.group.determinants[type].append(newDeterminant)
def findIterative(pair, iteratives):
"""
Function to find the two 'iteratives' that corresponds to the groups in 'pair'
"""
for iterative in iteratives:
if iterative.group == pair[0]:
iterative0 = iterative
elif iterative.group == pair[1]:
iterative1 = iterative
return iterative0, iterative1
class Iterative:
"""
Iterative class - pKa values and references of iterative groups
Note, this class has a fake determinant list, true determinants are
made after the iterations are finished.
"""
def __init__(self, group):
"""
Contructer of the iterative object
"""
#print "creating 'iterative object' for %s" % (group.label)
self.label = group.label
self.atom = group.atom
self.resName = group.residue_type
self.Q = group.charge
self.pKa_old = None
self.pKa_new = None
self.pKa_iter = []
self.pKa_NonIterative = 0.00
self.determinants = {'sidechain':[],'backbone':[],'coulomb':[]}
self.group = group
self.converged = True
# Calculate the Non-Iterative part of pKa from the group object
# Side chain
side_chain = 0.00
for determinant in group.determinants['sidechain']:
value = determinant.value
side_chain += value
# Back bone
back_bone = 0.00
for determinant in group.determinants['backbone']:
value = determinant.value
back_bone += value
# Coulomb
coulomb = 0.00
for determinant in group.determinants['coulomb']:
value = determinant.value
coulomb += value
self.pKa_NonIterative = group.model_pka
self.pKa_NonIterative += group.Emass
self.pKa_NonIterative += group.Elocl
self.pKa_NonIterative += side_chain
self.pKa_NonIterative += back_bone
self.pKa_NonIterative += coulomb
self.pKa_old = self.pKa_NonIterative
def __eq__(self, other):
"""
Check if two groups should be considered identical
"""
if self.atom.type == 'atom':
# In case of protein atoms we trust the labels
return self.label==other.label
else:
# For heterogene atoms we also need to check the residue number
return self.label==other.label and self.atom.resNumb == other.atom.resNumb
def __hash__(self):
""" Needed together with __eq__ - otherwise we can't make sets of groups """
return id(self)

198
Source/lib.py Executable file
View File

@@ -0,0 +1,198 @@
#!/usr/bin/python
import string, sys, copy, math, os
#
# file I/O
#
def open_file_for_reading(filename):
if not os.path.isfile(filename):
raise Exception('Cannot find file %s' %filename)
return open(filename,'r')
def open_file_for_writing(filename):
res = open(filename,'w')
if not res:
raise Exception('Could not open %s'%filename)
return res
#
# bookkeeping etc.
#
def conformation_sorter(conf):
model = int(conf[:-1])
altloc = conf[-1:]
return model*100+ord(altloc)
def split_atoms_into_molecules(atoms):
molecules = []
while len(atoms)>0:
initial_atom = atoms.pop()
molecules.append( make_molecule(initial_atom,atoms))
return molecules
def make_molecule(atom, atoms):
bonded_atoms = [a for a in atoms if atom in a.bonded_atoms]
res_atoms = [atom,]
for ba in bonded_atoms:
if ba in atoms:
atoms.remove(ba)
res_atoms.extend(make_molecule(ba, atoms))
return res_atoms
def make_grid(min,max,step):
x = min
while x <= max:
yield x
x += step
return
def generate_combinations(interactions):
res = [[]]
for interaction in interactions:
res = make_combination(res, interaction)
res.remove([])
return res
def make_combination(combis, interaction):
res = []
for combi in combis:
res.append(combi+[interaction])
res.append(combi)
return res
def loadOptions():
"""
load the arguments parser with options
"""
from optparse import OptionParser
# defining a 'usage' message
usage = "usage: %prog [options] filename"
# creating a parser
parser = OptionParser(usage)
# loading the parser
parser.add_option("-f", "--file", action="append", dest="filenames",
help="read data from <filename>, i.e. <filename> is added to arguments")
parser.add_option("-r", "--reference", dest="reference", default="neutral",
help="setting which reference to use for stability calculations [neutral/low-pH]")
parser.add_option("-c", "--chain", action="append", dest="chains",
help="creating the protein with only a specified chain, note, chains without ID are labeled 'A' [all]")
parser.add_option("-t", "--thermophile", action="append", dest="thermophiles",
help="defining a thermophile filename; usually used in 'alignment-mutations'")
parser.add_option("-a", "--alignment", action="append", dest="alignment",
help="alignment file connecting <filename> and <thermophile> [<thermophile>.pir]")
parser.add_option("-m", "--mutation", action="append", dest="mutations",
help="specifying mutation labels which is used to modify <filename> according to, e.g. N25R/N181D")
parser.add_option("-v", "--version", dest="version_label", default="Jan15",
help="specifying the sub-version of propka [Jan15/Dec19]")
parser.add_option("-p", "--parameters",dest="parameters", default="propka.cfg",
help="set the parameter file")
parser.add_option("-z", "--verbose", dest="verbose", action="store_true", default=True,
help="sleep during calculations")
parser.add_option("-q", "--quiet", dest="verbose", action="store_false",
help="sleep during calculations")
parser.add_option("-s", "--silent", dest="verbose", action="store_false",
help="not activated yet")
parser.add_option("--verbosity", dest="verbosity", action="store_const",
help="level of printout - not activated yet")
parser.add_option("-o", "--pH", dest="pH", type="float", default=7.0,
help="setting pH-value used in e.g. stability calculations [7.0]")
parser.add_option("-w", "--window", dest="window", nargs=3, type="float", default=(0.0, 14.0, 1.0),
help="setting the pH-window to show e.g. stability profiles [0.0, 14.0, 1.0]")
parser.add_option("-g", "--grid", dest="grid", nargs=3, type="float", default=(0.0, 14.0, 0.1),
help="setting the pH-grid to calculate e.g. stability related properties [0.0, 14.0, 0.1]")
parser.add_option("--mutator", dest="mutator",
help="setting approach for mutating <filename> [alignment/scwrl/jackal]")
parser.add_option("--mutator-option", dest="mutator_options", action="append",
help="setting property for mutator [e.g. type=\"side-chain\"]")
parser.add_option("-d","--display-coupled-residues", dest="display_coupled_residues", action="store_true",
help="Displays alternative pKa values due to coupling of titratable groups")
parser.add_option("-l","--reuse-ligand-mol2-files", dest="reuse_ligand_mol2_file", action="store_true",
help="Reuses the ligand mol2 files allowing the user to alter ligand bond orders", default=False)
parser.add_option("-k","--keep-protons", dest="keep_protons", action="store_true",
help="Keep protons in input file", default=False)
parser.add_option("--protonate-all", dest="protonate_all", action="store_true",
help="Protonate all atoms (will not influence pKa calculation)", default=False)
# parsing and returning options and arguments
options, args = parser.parse_args()
# adding specified filenames to arguments
if options.filenames:
for filename in options.filenames:
args.append(filename)
# checking at early stage that there is at least one pdbfile to work with
if len(args) == 0:
print("Warning: no pdbfile provided")
#sys.exit(9)
# done!
return options, args
def makeTidyAtomLabel(name,element):
"""
Returns a 'tidier' atom label for printing the new pdbfile
"""
if len(name)>4:# if longer than 4, just truncate the name
label=name[0:4]
elif len(name)==4:# if lenght is 4, otherwise use the name as it is
label = name
else: # if less than 4 characters long, insert white space as needed
if len(element)==1:
label = ' %-3s'%name
else: # The element shoul occupy the two first chars
label = '%-4s'%name
return label
def get_sorted_configurations(configuration_keys):
"""
extract and sort configurations
"""
configurations = list(configuration_keys)
configurations.sort(key=configuration_compare)
return configurations
def configuration_compare(conf):
return 100*int(conf[1:-2]) + ord(conf[-1])
def writeFile(filename, lines):
"""
Writes a new file
"""
file = open(filename, 'w')
for line in lines:
file.write( "%s\n" % (line) )
file.close()

403
Source/ligand.py Executable file
View File

@@ -0,0 +1,403 @@
#!/usr/bin/python
import sys, Source.calculations
from Source.vector_algebra import *
all_sybyl_types = [
'C.3', # carbon sp3
'H', # hydrogen
'C.2', # carbon sp2
'H.spc', # hydrogen in Single Point Charge (SPC) water model
'C.1', # carbon sp
'H.t3p', # hydrogen in Transferable intermolecular Potential (TIP3P) water model
'C.ar', # carbon aromatic
'LP', # lone pair
'C.cat', # carbocation (C+) used only in a guadinium group
'Du', # dummy atom
'N.3', # nitrogen sp3
'Du.C', # dummy carbon
'N.2', # nitrogen sp2
'Any', # any atom
'N.1', # nitrogen sp
'Hal', # halogen
'N.ar', # nitrogen aromatic
'Het', # heteroatom = N, O, S, P
'N.am', # nitrogen amide
'Hev', # heavy atom (non hydrogen)
'N.pl3', # nitrogen trigonal planar
'Li', # lithium
'N.4', # nitrogen sp3 positively charged
'Na', # sodium
'O.3', # oxygen sp3
'Mg', # magnesium
'O.2', # oxygen sp2
'Al', # aluminum
'O.co2', # oxygen in carboxylate and phosphate groups
'Si', # silicon
'O.spc', # oxygen in Single Point Charge (SPC) water model
'K', # potassium
'O.t3p', # oxygen in Transferable Intermolecular Potential (TIP3P) water model
'Ca', # calcium
'S.3', # sulfur sp3
'Cr.th', # chromium (tetrahedral)
'S.2', # sulfur sp2
'Cr.oh', # chromium (octahedral)
'S.O', # sulfoxide sulfur
'Mn', # manganese
'S.O2', # sulfone sulfur
'Fe', # iron
'P.3', # phosphorous sp3
'Co.oh', # cobalt (octahedral)
'F', # fluorine
'Cu', # copper
'Cl', # chlorine
'Zn', # zinc
'Br', # bromine
'Se', # selenium
'I', # iodine
'Mo', # molybdenum
'Sn'] # tin
#propka_input_types = ['1P','1N','2P','2N']
#for type in all_sybyl_types:
# temp = type.replace('.','')
# if len(temp)>3:
# temp = temp[0:3]
# propka_input_types.append(temp)
#
#for t in propka_input_types:
# print (t)
propka_input_types = [
'1P',
'1N',
'2P',
'2N',
'C3',
'H',
'C2',
'Hsp',
'C1',
'Ht3',
'Car',
'LP',
'Cca',
'Du',
'N3',
'DuC',
'N2',
'Any',
'N1',
'Hal',
'Nar',
'Het',
'Nam',
'Hev',
'Npl',
'Li',
'N4',
'Na',
'O3',
'Mg',
'O2',
'Al',
'Oco',
'Si',
'Osp',
'K',
'Ot3',
'Ca',
'S3',
'Crt',
'S2',
'Cro',
'SO',
'Mn',
'SO2',
'Fe',
'P3',
'Coo',
'F',
'Cu',
'Cl',
'Zn',
'Br',
'Se',
'I',
'Mo',
'Sn']
max_C_double_bond = 1.3
max_C_triple_bond = 1.2
max_C_double_bond_squared = max_C_double_bond*max_C_double_bond
max_C_triple_bond_squared = max_C_triple_bond*max_C_triple_bond
def assign_sybyl_type(atom):
# check if we already have assigned a name to this atom
if atom.sybyl_assigned:
#print(atom.name,'already assigned')
return
# find some properties of the atom
ring_atoms = is_ring_member(atom)
aromatic = is_aromatic_ring(ring_atoms)
planar = is_planar(atom)
bonded_elements = {}
for i in range(len(atom.bonded_atoms)):
bonded_elements[i]=atom.bonded_atoms[i].element
# Aromatic carbon/nitrogen
if aromatic:
for ra in ring_atoms:
if ra.element in ['C','N']:
set_type(ra, ra.element+'.ar')
return
# check for amide
if atom.element in ['O','N','C']:
O=None
N=None
C=None
# oxygen, nitrogen
if atom.element in ['O','N']:
for b in atom.get_bonded_elements('C'):
for bb in b.bonded_atoms:
if (bb.element =='N' and atom.element == 'O'):
O=atom
C=b
N=bb
elif (bb.element =='O' and atom.element == 'N'):
N=atom
C=b
O=bb
# carbon
if atom.element == 'C':
nitrogens = atom.get_bonded_elements('N')
oxygens = atom.get_bonded_elements('O')
if len(nitrogens)==1 and len(oxygens)==1:
C = atom
N = nitrogens[0]
O = oxygens[0]
if C and N and O:
# make sure that the Nitrogen is not aromatic and that it has two heavy atom bonds
if not is_aromatic_ring(is_ring_member(N)) and len(N.get_bonded_heavy_atoms())==2:
set_type(N,'N.am')
set_type(C,'C.2')
set_type(O,'O.2')
return
if atom.element=='C':
# check for carboxyl
if len(atom.bonded_atoms)==3 and list(bonded_elements.values()).count('O')==2:
i1 = list(bonded_elements.values()).index('O')
i2 = list(bonded_elements.values()).index('O',i1+1)
if len(atom.bonded_atoms[i1].bonded_atoms)==1 and len(atom.bonded_atoms[i2].bonded_atoms)==1:
set_type(atom.bonded_atoms[i1],'O.co2-')
set_type(atom.bonded_atoms[i2],'O.co2')
set_type(atom,'C.2')
return
# sp carbon
if len(atom.bonded_atoms)<=2:
for b in atom.bonded_atoms:
if Source.calculations.squared_distance(atom, b) < max_C_triple_bond_squared:
set_type(atom,'C.1')
set_type(b,b.element+'.1')
if atom.sybyl_assigned:
return
# sp2 carbon
if planar:
set_type(atom,'C.2')
# check for N.pl3
for b in atom.bonded_atoms:
if b.element=='N':
if len(b.bonded_atoms)<3 or is_planar(b):
set_type(b,'N.pl3')
return
# sp3 carbon
set_type(atom, 'C.3')
return
# Nitrogen
if atom.element == 'N':
# check for planar N
if len(atom.bonded_atoms)==1:
if is_planar(atom.bonded_atoms[0]):
set_type(atom,'N.pl3')
return
if planar:
set_type(atom,'N.pl3')
return
set_type(atom,'N.3')
return
# Oxygen
if atom.element == 'O':
set_type(atom,'O.3')
if len(atom.bonded_atoms) == 1:
# check for carboxyl
if atom.bonded_atoms[0].element == 'C':
the_carbon = atom.bonded_atoms[0]
if len(the_carbon.bonded_atoms)==3 and the_carbon.count_bonded_elements('O')==2:
[O1,O2] = the_carbon.get_bonded_elements('O')
if len(O1.bonded_atoms)==1 and len(O2.bonded_atoms)==1:
set_type(O1,'O.co2-')
set_type(O2,'O.co2')
set_type(the_carbon,'C.2')
return
# check for X=O
if Source.calculations.squared_distance(atom, atom.bonded_atoms[0]) < max_C_double_bond_squared:
set_type(atom,'O.2')
if atom.bonded_atoms[0].element=='C':
set_type(atom.bonded_atoms[0],'C.2')
return
# Sulphur
if atom.element == 'S':
# check for SO2
if list(bonded_elements.values()).count('O')==2:
i1 = list(bonded_elements.values()).index('O')
i2 = list(bonded_elements.values()).index('O',i1+1)
set_type(atom.bonded_atoms[i1],'O.2')
set_type(atom.bonded_atoms[i2],'O.2')
set_type(atom,'S.o2')
return
# check for SO4
if list(bonded_elements.values()).count('O')==4:
no_O2 = 0
for i in range(len(atom.bonded_atoms)):
if len(atom.bonded_atoms[i].bonded_atoms)==1 and no_O2<2:
set_type(atom.bonded_atoms[i],'O.2')
no_O2+=1
else:
set_type(atom.bonded_atoms[i],'O.3')
set_type(atom,'S.3')
return
# Phosphorus
if atom.element == 'P':
set_type(atom,'P.3')
# check for phosphate group
bonded_oxygens = atom.get_bonded_elements('O')
for o in bonded_oxygens: set_type(o,'O.3')
# if len(bonded_oxygens)>=3:
# # find oxygens only bonded to current phosphorus
# bonded_oxygens_1 = [o for o in bonded_oxygens if len(o.get_bonded_heavy_atoms())==1]
# # find the closest oxygen ...
# closest_oxygen = min(bonded_oxygens_1,
# key= lambda o:Source.calculations.squared_distance(atom,o))
# # ... and set it to O.2
# set_type(closest_oxygen,'O.2')
return
element = atom.element.capitalize()
set_type(atom,element)
# print('Using element as type for %s'%atom.element)
return
def is_ring_member(atom):
return identify_ring(atom,atom,0,[])
def identify_ring(this_atom, original_atom, number, past_atoms):
number+=1
past_atoms=past_atoms+[this_atom]
return_atoms = []
if number > 10:
return return_atoms
for atom in this_atom.get_bonded_heavy_atoms():
if atom == original_atom and number>2:
return past_atoms
if atom not in past_atoms:
these_return_atoms = identify_ring(atom, original_atom, number, past_atoms)
if len(these_return_atoms) > 0:
if len(return_atoms)>len(these_return_atoms) or len(return_atoms)==0:
return_atoms = these_return_atoms
return return_atoms
def set_type(atom,type):
#print(atom, '->',type)
atom.sybyl_type = type
atom.sybyl_assigned=True
return
def is_planar(atom):
""" Finds out if atom forms a plane together with its bonded atoms"""
atoms = [atom]+atom.bonded_atoms
return are_atoms_planar(atoms)
def are_atoms_planar(atoms):
if len(atoms)==0:
return False
if len(atoms)<4:
return False
v1 = vector(atom1=atoms[0], atom2=atoms[1])
v2 = vector(atom1=atoms[0], atom2=atoms[2])
n = (v1**v2).rescale(1.0)
margin = 0.20
for b in atoms[3:]:
v = vector(atom1=atoms[0], atom2=b).rescale(1.0)
#print(atoms[0],abs(v*n) )
if abs(v*n)>margin:
return False
return True
def is_aromatic_ring(atoms):
if len(atoms)<5:
return False
for i in range(len(atoms)):
if not are_atoms_planar(atoms[i:]+atoms[:i]):
return False
return True

118
Source/ligand_pka_values.py Normal file
View File

@@ -0,0 +1,118 @@
#!/usr/bin/env python
import Source.molecular_container, Source.calculations, Source.calculations, Source.parameters, Source.pdb, Source.lib, os, subprocess, sys
class ligand_pka_values:
def __init__(self, parameters):
self.parameters = parameters
# attempt to find Marvin executables in the path
self.molconvert = self.find_in_path('molconvert')
self.cxcalc = self.find_in_path('cxcalc')
print('Found Marvin executables:')
print(self.cxcalc)
print(self.molconvert)
return
def find_in_path(self, program):
path = os.environ.get('PATH').split(os.pathsep)
l = [i for i in filter(lambda loc: os.access(loc, os.F_OK),
map(lambda dir: os.path.join(dir, program),path))]
if len(l) == 0:
print('Error: Could not find %s. Please make sure that it is found in the path.'%program)
sys.exit(-1)
return l[0]
def get_marvin_pkas_for_pdb_file(self, file, no_pkas=10, min_pH =-10, max_pH=20):
molecule = Source.molecular_container.Molecular_container(file)
self.get_marvin_pkas_for_molecular_container(molecule, no_pkas=no_pkas, min_pH =min_pH, max_pH=max_pH)
return
def get_marvin_pkas_for_molecular_container(self, molecule, no_pkas=10, min_pH =-10, max_pH=20):
for name in molecule.conformation_names:
filename = '%s_%s'%(molecule.name,name)
self.get_marvin_pkas_for_conformation_container(molecule.conformations[name], name=filename, reuse=molecule.options.reuse_ligand_mol2_file,
no_pkas=no_pkas, min_pH =min_pH, max_pH=max_pH)
return
def get_marvin_pkas_for_conformation_container(self, conformation, name = 'temp', reuse=False, no_pkas=10, min_pH =-10, max_pH=20):
conformation.marvin_pkas_calculated = True
self.get_marvin_pkas_for_atoms(conformation.get_heavy_ligand_atoms(), name=name, reuse=reuse,
no_pkas=no_pkas, min_pH =min_pH, max_pH=max_pH)
return
def get_marvin_pkas_for_atoms(self, atoms, name='temp', reuse=False, no_pkas=10, min_pH =-10, max_pH=20):
# do one molecule at the time so we don't confuse marvin
molecules = Source.lib.split_atoms_into_molecules(atoms)
for i in range(len(molecules)):
filename = '%s_%d.mol2'%(name, i+1)
self.get_marvin_pkas_for_molecule(molecules[i], filename=filename, reuse=reuse, no_pkas=no_pkas, min_pH =min_pH, max_pH=max_pH)
return
def get_marvin_pkas_for_molecule(self, atoms, filename='__tmp_ligand.mol2', reuse=False, no_pkas=10, min_pH =-10, max_pH=20):
# print out structure unless we are using user-modified structure
if not reuse:
Source.pdb.write_mol2_for_atoms(atoms, filename)
# check that we actually have a file to work with
if not os.path.isfile(filename):
print('Warning: Didn\'t find a user-modified file \'%s\' - generating one'%filename)
Source.pdb.write_mol2_for_atoms(atoms, filename)
# Marvin
# calculate pKa values
options = 'pka -a %d -b %d --min %f --max %f -d large'%(no_pkas, no_pkas, min_pH, max_pH)
(output,errors) = subprocess.Popen([self.cxcalc, filename]+options.split(),
stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
if len(errors)>0:
print('********************************************************************************************************')
print('* Warning: Marvin execution failed: *')
print('* %-100s *'%errors)
print('* *')
print('* Please edit the ligand mol2 file and re-run PropKa with the -l option: %29s *'%filename)
print('********************************************************************************************************')
sys.exit(-1)
# extract calculated pkas
indices,pkas,types = self.extract_pkas(output)
# store calculated pka values
for i in range(len(indices)):
atoms[indices[i]].marvin_pka = pkas[i]
atoms[indices[i]].charge = {'a':-1,'b':+1}[types[i]]
print('%s model pKa: %.2f'%(atoms[indices[i]],pkas[i]))
return
def extract_pkas(self, output):
# split output
[tags, values,empty_line] = output.decode().split('\n')
#print(tags)
#print(values)
tags = tags.split('\t')
values = values.split('\t')
# format values
types = [tags[i][0] for i in range(1,len(tags)-1) if len(values)>i and values[i] != '']
indices = [int(a)-1 for a in values[-1].split(',') if a !='']
values = [float(v.replace(',','.')) for v in values[1:-1] if v != '']
if len(indices) != len(values) != len(types):
raise Exception('Lengths of atoms and pka values mismatch')
return indices, values, types

247
Source/molecular_container.py Executable file
View File

@@ -0,0 +1,247 @@
#!/usr/bin/python
#
# Molecular container for storing all contents of pdb files
#
#
import os, Source.pdb, sys, Source.version, Source.output, Source.conformation_container, Source.group, Source.lib
class Molecular_container:
def __init__(self, input_file, options=None):
# printing out header before parsing input
Source.output.printHeader()
# set up some values
self.options = options
self.input_file = input_file
self.dir = os.path.split(input_file)[0]
self.file = os.path.split(input_file)[1]
self.name = self.file[0:self.file.rfind('.')]
input_file_extension = input_file[input_file.rfind('.'):]
# set the version
if options:
parameters = Source.parameters.Parameters(self.options.parameters)
else:
parameters = Source.parameters.Parameters('propka.cfg')
try:
exec('self.version = Source.version.%s(parameters)'%parameters.version)
except:
raise Exception('Error: Version %s does not exist'%parameters.version)
# read the input file
if input_file_extension[0:4] == '.pdb':
# input is a pdb file
# read in atoms and top up containers to make sure that all atoms are present in all conformations
[self.conformations, self.conformation_names] = Source.pdb.read_pdb(input_file, self.version.parameters,self)
if len(self.conformations)==0:
print('Error: The pdb file does not seems to contain any molecular conformations')
sys.exit(-1)
self.top_up_conformations()
# make a structure precheck
Source.pdb.protein_precheck(self.conformations, self.conformation_names)
# set up atom bonding and protonation
self.version.setup_bonding_and_protonation(self)
# Extract groups
self.extract_groups()
# sort atoms
for name in self.conformation_names:
self.conformations[name].sort_atoms()
# find coupled groups
self.find_covalently_coupled_groups()
# write out the input file
filename = self.file.replace(input_file_extension,'.propka_input')
Source.pdb.write_input(self, filename)
elif input_file_extension == '.propka_input':
#input is a propka_input file
[self.conformations, self.conformation_names] = Source.pdb.read_input(input_file, self.version.parameters, self)
# Extract groups - this merely sets up the groups found in the input file
self.extract_groups()
# do some additional set up
self.additional_setup_when_reading_input_file()
else:
print('Unrecognized input file:%s'%input_file)
sys.exit(-1)
return
def top_up_conformations(self):
""" Makes sure that all atoms are present in all conformations """
for name in self.conformation_names:
if name!='1A' and (len(self.conformations[name]) < len(self.conformations['1A'])):
self.conformations[name].top_up(self.conformations['1A'])
return
def find_covalently_coupled_groups(self):
print('-'*103)
for name in self.conformation_names:
self.conformations[name].find_covalently_coupled_groups()
return
def find_non_covalently_coupled_groups(self):
print('-'*103)
for name in self.conformation_names:
self.conformations[name].find_non_covalently_coupled_groups(verbose=self.options.display_coupled_residues)
return
def extract_groups(self):
""" Identify the groups needed for pKa calculation """
for name in self.conformation_names:
self.conformations[name].extract_groups()
return
def additional_setup_when_reading_input_file(self):
for name in self.conformation_names:
self.conformations[name].additional_setup_when_reading_input_file()
return
def calculate_pka(self):
# calculate for each conformation
for name in self.conformation_names:
self.conformations[name].calculate_pka(self.version, self.options)
# find non-covalently coupled groups
self.find_non_covalently_coupled_groups()
# find the average of the conformations
self.average_of_conformations()
# print out the conformation-average results
Source.output.printResult(self, 'AVR', self.version.parameters)
return
def average_of_conformations(self):
# make a new configuration to hold the average values
avr_conformation = Source.conformation_container.Conformation_container(name='average',
parameters=self.conformations[self.conformation_names[0]].parameters,
molecular_container=self)
for group in self.conformations[self.conformation_names[0]].get_titratable_groups_and_cysteine_bridges():
# new group to hold average values
avr_group = group.clone()
# sum up all groups ...
for name in self.conformation_names:
group_to_add = self.conformations[name].find_group(group)
if group_to_add:
avr_group += group_to_add
else:
print('Warning: Group %s could not be found in conformation %s.'%(group.atom.residue_label, name))
# ... and store the average value
avr_group = avr_group / len(self.conformation_names)
avr_conformation.groups.append(avr_group)
# store information on coupling in the average container
if len(list(filter(lambda c: c.non_covalently_coupled_groups, self.conformations.values()))):
avr_conformation.non_covalently_coupled_groups = True
# store chain info
avr_conformation.chains = self.conformations[self.conformation_names[0]].chains
self.conformations['AVR'] = avr_conformation
return
def write_pka(self, filename=None, reference="neutral", direction="folding", options=None):
#for name in self.conformation_names:
# Source.output.writePKA(self, self.version.parameters, filename='%s_3.1_%s.pka'%(self.name, name),
# conformation=name,reference=reference,
# direction=direction, options=options)
# write out the average conformation
filename=os.path.join('%s.pka'%(self.name))
# if the display_coupled_residues option is true,
# write the results out to an alternative pka file
if self.options.display_coupled_residues:
filename=os.path.join('%s_alt_state.pka'%(self.name))
if hasattr(self.version.parameters, 'output_file_tag') and len(self.version.parameters.output_file_tag)>0:
filename=os.path.join('%s_%s.pka'%(self.name,self.version.parameters.output_file_tag))
Source.output.writePKA(self, self.version.parameters, filename=filename,
conformation='AVR',reference=reference,
direction=direction, options=options)
return
def getFoldingProfile(self, conformation='AVR',reference="neutral", direction="folding", grid=[0., 14., 0.1], options=None):
# calculate stability profile
profile = []
for ph in Source.lib.make_grid(*grid):
ddg = self.conformations[conformation].calculate_folding_energy( pH=ph, reference=reference)
#print(ph,ddg)
profile.append([ph, ddg])
# find optimum
opt =[None, 1e6]
for point in profile:
opt = min(opt, point, key=lambda v:v[1])
# find values within 80 % of optimum
range_80pct = [None, None]
values_within_80pct = [p[0] for p in profile if p[1]< 0.8*opt[1]]
if len(values_within_80pct)>0:
range_80pct = [min(values_within_80pct), max(values_within_80pct)]
# find stability range
stability_range = [None, None]
stable_values = [p[0] for p in profile if p[1]< 0.0]
if len(stable_values)>0:
stability_range = [min(stable_values), max(stable_values)]
return profile, opt, range_80pct, stability_range
def getChargeProfile(self, conformation='AVR', grid=[0., 14., .1]):
charge_profile = []
for ph in Source.lib.make_grid(*grid):
q_unfolded, q_folded = self.conformations[conformation].calculate_charge(self.version.parameters, pH=ph)
charge_profile.append([ph, q_unfolded, q_folded])
return charge_profile
def getPI(self, conformation='AVR', grid=[0., 14., 1], iteration=0):
#print('staring',grid, iteration)
# search
charge_profile = self.getChargeProfile(conformation=conformation, grid=grid)
pi = []
pi_folded = pi_unfolded = [None, 1e6,1e6]
for point in charge_profile:
pi_folded = min(pi_folded, point, key=lambda v:abs(v[2]))
pi_unfolded = min(pi_unfolded, point, key=lambda v:abs(v[1]))
# If results are not good enough, do it again with a higher sampling resolution
pi_folded_value = pi_folded[0]
pi_unfolded_value = pi_unfolded[0]
step = grid[2]
if (pi_folded[2] > 0.01 or pi_unfolded[1] > 0.01) and iteration<4:
pi_folded_value, x = self.getPI(conformation=conformation, grid=[pi_folded[0]-step, pi_folded[0]+step, step/10.0], iteration=iteration+1)
x, pi_unfolded_value = self.getPI(conformation=conformation, grid=[pi_unfolded[0]-step, pi_unfolded[0]+step, step/10.0], iteration=iteration+1)
return pi_folded_value, pi_unfolded_value
if __name__ == '__main__':
input_file = sys.argv[1]
mc = Molecular_container(input_file)

391
Source/output.py Normal file
View File

@@ -0,0 +1,391 @@
import sys
import Source.lib
def printHeader():
"""
prints the header section
"""
str = "%s\n" % ( getPropkaHeader() )
str += "%s\n" % ( getReferencesHeader() )
str += "%s\n" % ( getWarningHeader() )
print(str)
def writePDB(protein, file=None, filename=None, include_hydrogens=False, options=None):
"""
Write the residue to the new pdbfile
"""
if file == None:
# opening file if not given
if filename == None:
filename = "%s.pdb" % (protein.name)
file = open(filename, 'w')
print("writing pdbfile %s" % (filename))
close_file = True
else:
# don't close the file, it was opened in a different place
close_file = False
numb = 0
for chain in protein.chains:
for residue in chain.residues:
if residue.resName not in ["N+ ", "C- "]:
for atom in residue.atoms:
if include_hydrogens == False and atom.name[0] == "H":
""" don't print """
else:
numb += 1
line = atom.makePDBLine(numb=numb)
line += "\n"
file.write(line)
if close_file == True:
file.close()
def writePKA(protein, parameters, filename=None, conformation ='1A',reference="neutral", direction="folding", verbose=False, options=None):
"""
Write the pka-file based on the given protein
"""
verbose = True
if filename == None:
filename = "%s.pka" % (protein.name)
file = open(filename, 'w')
if verbose == True:
print("Writing %s" % (filename))
# writing propka header
str = "%s\n" % ( getPropkaHeader() )
str += "%s\n" % ( getReferencesHeader() )
str += "%s\n" % ( getWarningHeader() )
# writing pKa determinant section
str += getDeterminantSection(protein,conformation, parameters)
# writing pKa summary section
str += getSummarySection(protein,conformation,parameters)
str += "%s\n" % ( getTheLine() )
# printing Folding Profile
str += getFoldingProfileSection(protein, conformation=conformation, reference=reference, direction=direction, window=[0., 14., 1.0], options=options)
# printing Protein Charge Profile
str += getChargeProfileSection(protein, conformation=conformation)
# now, writing the pka text to file
file.write(str)
file.close()
def printTmProfile(protein, reference="neutral", window=[0., 14., 1.], Tm=[0.,0.], Tms=None, ref=None, verbose=False, options=None):
"""
prints Tm profile
"""
profile = protein.getTmProfile(reference=reference, grid=[0., 14., 0.1], Tms=Tms, ref=ref, options=options)
if profile == None:
str = "Could not determine Tm-profile\n"
else:
str = " suggested Tm-profile for %s\n" % (protein.name)
for (pH, Tm) in profile:
if pH >= window[0] and pH <= window[1] and (pH%window[2] < 0.01 or pH%window[2] > 0.99*window[2]):
str += "%6.2lf%10.2lf\n" % (pH, Tm)
print(str)
def printResult(protein, conformation, parameters):
"""
prints all resulting output from determinants and down
"""
printPKASection(protein, conformation, parameters)
def printPKASection(protein, conformation, parameters):
"""
prints out the pka-section of the result
"""
# geting the determinants section
str = getDeterminantSection(protein, conformation, parameters)
print(str)
str = getSummarySection(protein,conformation,parameters)
print(str)
def getDeterminantSection(protein, conformation, parameters):
"""
prints out the pka-section of the result
"""
# getting the same order as in propka2.0
str = "%s\n" % ( getDeterminantsHeader() )
# printing determinants
for chain in protein.conformations[conformation].chains:
for residue_type in parameters.write_out_order:
groups = [g for g in protein.conformations[conformation].groups if g.atom.chainID == chain]
for group in groups:
if group.residue_type == residue_type:
str += "%s" % ( group.getDeterminantString(parameters.remove_penalised_group) )
# Add a warning in case of coupled residues
if protein.conformations[conformation].non_covalently_coupled_groups and not protein.options.display_coupled_residues:
str += 'Coupled residues (marked *) were detected. Please rerun PropKa with the --display-coupled-residues \nor -d option for detailed information.\n'
return str
def getSummarySection(protein, conformation, parameters):
"""
prints out the pka-section of the result
"""
str = "%s\n" % ( getSummaryHeader() )
# printing pKa summary
for residue_type in parameters.write_out_order:
for group in protein.conformations[conformation].groups:
if group.residue_type == residue_type:
str += "%s" % ( group.getSummaryString(parameters.remove_penalised_group) )
return str
def getFoldingProfileSection(protein, conformation='AVR', direction="folding", reference="neutral", window=[0., 14., 1.0], verbose=False, options=None):
"""
returns the protein-folding-profile section
"""
str = getTheLine()
str += "\n"
str += "Free energy of %9s (kcal/mol) as a function of pH (using %s reference)\n" % (direction, reference)
profile, [pH_opt, dG_opt], [dG_min, dG_max], [pH_min, pH_max] = protein.getFoldingProfile(conformation=conformation,
reference=reference,
direction=direction, grid=[0., 14., 0.1], options=options)
if profile == None:
str += "Could not determine folding profile\n"
else:
for (pH, dG) in profile:
if pH >= window[0] and pH <= window[1] and (pH%window[2] < 0.05 or pH%window[2] > 0.95):
str += "%6.2lf%10.2lf\n" % (pH, dG)
str += "\n"
if pH_opt == None or dG_opt == None:
str += "Could not determine pH optimum\n"
else:
str += "The pH of optimum stability is %4.1lf for which the free energy is%6.1lf kcal/mol at 298K\n" % (pH_opt, dG_opt)
if dG_min == None or dG_max == None:
str += "Could not determine pH values where the free energy is within 80 %s of minimum\n" % ("%")
else:
str += "The free energy is within 80 %s of maximum at pH %4.1lf to %4.1lf\n" % ("%", dG_min, dG_max)
if pH_min == None or pH_max == None:
str += "Could not determine the pH-range where the free energy is negative\n\n"
else:
str += "The free energy is negative in the range %4.1lf - %4.1lf\n\n" % (pH_min, pH_max)
return str
def getChargeProfileSection(protein, conformation='AVR', options=None):
"""
returns the protein-folding-profile section
"""
str = "Protein charge of folded and unfolded state as a function of pH\n"
profile = protein.getChargeProfile(conformation=conformation,grid=[0., 14., 1.])
if profile == None:
str += "Could not determine charge profile\n"
else:
str += "%6s%10s%8s\n" % ("pH", "unfolded", "folded")
for (pH, Q_mod, Q_pro) in profile:
str += "%6.2lf%10.2lf%8.2lf\n" % (pH, Q_mod, Q_pro)
pI_pro, pI_mod = protein.getPI(conformation=conformation)
if pI_pro == None or pI_mod == None:
str += "Could not determine the pI\n\n"
else:
str += "The pI is %5.2lf (folded) and %5.2lf (unfolded)\n" % (pI_pro, pI_mod)
return str
def writeJackalScapFile(mutationData=None, filename="1xxx_scap.list", options=None):
"""
writing a scap file for, i.e., generating a mutated protein
"""
file = open(filename, 'w')
for chainID, code1, resNumb, code2 in mutationData:
str = "%s, %d, %s\n" % (chainID, resNumb, code2)
file.write(str)
file.close()
def writeScwrlSequenceFile(sequence, filename="x-ray.seq", options=None):
"""
writing a scwrl sequence file for, e.g., generating a mutated protein
"""
file = open(filename, 'w')
start = 0
while len(sequence[start:]) > 60:
file.write( "%s\n" % (sequence[start:start+60]) )
start += 60
file.write( "%s\n" % (sequence[start:]) )
file.close()
# --- various header text --- #
def getPropkaHeader():
"""
Creates the header
"""
from datetime import date
today = date.today()
str = "propka3.1 %93s\n" % (today)
str += "-------------------------------------------------------------------------------------------------------\n"
str += "-- --\n"
str += "-- PROPKA: A PROTEIN PKA PREDICTOR --\n"
str += "-- --\n"
str += "-- VERSION 1.0, 04/25/2004, IOWA CITY --\n"
str += "-- BY HUI LI --\n"
str += "-- --\n"
str += "-- VERSION 2.0, 11/05/2007, IOWA CITY/COPENHAGEN --\n"
str += "-- BY DELPHINE C. BAS AND DAVID M. ROGERS --\n"
str += "-- --\n"
str += "-- VERSION 3.0, 01/06/2011, COPENHAGEN --\n"
str += "-- BY MATS H.M. OLSSON AND CHRESTEN R. SONDERGARD --\n"
str += "-- --\n"
str += "-- VERSION 3.1, 07/01/2011, COPENHAGEN --\n"
str += "-- BY CHRESTEN R. SONDERGARD AND MATS H.M. OLSSON --\n"
str += "-------------------------------------------------------------------------------------------------------\n"
str += "\n"
return str
def getReferencesHeader():
"""
Returns the 'references' part in output file
"""
str = ""
str += "-------------------------------------------------------------------------------------------------------\n"
str += " References:\n"
str += "\n"
str += " Very Fast Empirical Prediction and Rationalization of Protein pKa Values\n"
str += " Hui Li, Andrew D. Robertson and Jan H. Jensen\n"
str += " PROTEINS: Structure, Function, and Bioinformatics 61:704-721 (2005)\n"
str += " \n"
str += " Very Fast Prediction and Rationalization of pKa Values for Protein-Ligand Complexes\n"
str += " Delphine C. Bas, David M. Rogers and Jan H. Jensen\n"
str += " PROTEINS: Structure, Function, and Bioinformatics 73:765-783 (2008)\n"
str += " \n"
str += " PROPKA3: Consistent Treatment of Internal and Surface Residues in Empirical pKa predictions\n"
str += " Mats H.M. Olsson, Chresten R. Sondergard, Michal Rostkowski, and Jan H. Jensen\n"
str += " Journal of Chemical Theory and Computation, 7(2):525-537 (2011)\n"
str += " \n"
str += " Improved Treatment of Ligands and Coupling Effects in Empirical Calculation\n"
str += " and Rationalization of pKa Values\n"
str += " Chresten R. Sondergaard, Mats H.M. Olsson, Michal Rostkowski, and Jan H. Jensen\n"
str += " Journal of Chemical Theory and Computation, (2011)\n"
str += " \n"
str += "-------------------------------------------------------------------------------------------------------\n"
return str
def getWarningHeader():
"""
Returns the 'warning' part in output file
"""
str = ""
return str
def getDeterminantsHeader():
"""
Creates the Determinant header
"""
str = ""
str += "--------- ----- ------ --------------------- -------------- -------------- --------------\n"
str += " DESOLVATION EFFECTS SIDECHAIN BACKBONE COULOMBIC \n"
str += " RESIDUE pKa BURIED REGULAR RE HYDROGEN BOND HYDROGEN BOND INTERACTION \n"
str += "--------- ----- ------ --------- --------- -------------- -------------- --------------\n"
return str
def getSummaryHeader():
"""
returns the summary header
"""
str = getTheLine()
str += "\n"
str += "SUMMARY OF THIS PREDICTION\n"
str += " Group pKa model-pKa ligand atom-type"
return str
def getTheLine():
"""
draw the line - Johnny Cash would have been proud - or actually Aerosmith!
"""
str = ""
for i in range(0, 104):
str += "-"
return str
# Interaction maps
def make_interaction_map(name, list, interaction):
""" Print out an interaction map named 'name' of the groups in 'list'
based on the function 'interaction' """
# return an empty string, if the list is empty
if len(list)==0:
return ''
# for long list, use condensed formatting
if len(list)>10:
res = 'Condensed form:\n'
for i in range(len(list)):
for j in range(i,len(list)):
if interaction(list[i],list[j]):
res += 'Coupling: %9s - %9s\n'%(list[i].label,list[j].label)
return res
# Name and map header
res = '%s\n%12s'%(name,'')
for g in list:
res += '%9s | '%g.label
# do the map
for g1 in list:
res += '\n%-12s'%(g1.label)
for g2 in list:
tag = ''
if interaction(g1, g2):
tag = ' X '
res += '%10s| '%tag
return res

529
Source/parameters.py Normal file
View File

@@ -0,0 +1,529 @@
import math
import Source.lib as lib
import sys, os
# names and types of all key words in configuration file
matrices = ['interaction_matrix']
pair_wise_matrices = ['sidechain_cutoffs']
number_dictionaries = ['VanDerWaalsVolume','charge','model_pkas','ions',
'valence_electrons','custom_model_pkas']
list_dictionaries = ['backbone_NH_hydrogen_bond','backbone_CO_hydrogen_bond']
string_dictionaries = ['protein_group_mapping']
string_lists = ['ignore_residues','angular_dependent_sidechain_interactions',
'acid_list','base_list','exclude_sidechain_interactions',
'backbone_reorganisation_list','write_out_order']
distances = ['desolv_cutoff','buried_cutoff','coulomb_cutoff1','coulomb_cutoff2']
parameters = ['Nmin','Nmax','desolvationSurfaceScalingFactor','desolvationPrefactor',
'desolvationAllowance','coulomb_diel','COO_HIS_exception','OCO_HIS_exception',
'CYS_HIS_exception','CYS_CYS_exception','min_ligand_model_pka','max_ligand_model_pka',
'include_H_in_interactions','coupling_max_number_of_bonds',
'min_bond_distance_for_hydrogen_bonds','coupling_penalty',
'shared_determinants','common_charge_centre','hide_penalised_group', 'remove_penalised_group',
'max_intrinsic_pKa_diff','min_interaction_energy','max_free_energy_diff','min_swap_pka_shift',
'min_pka','max_pka','sidechain_interaction']
strings = ['version','output_file_tag','ligand_typing','pH','reference']
class Parameters:
def __init__(self, parameter_file):
self.set_up_data_structures()
self.read_parameters(parameter_file)
#self.print_interaction_parameters()
#self.print_interaction_parameters_latex()
#####self.print_interactions_latex()
#sys.exit(0)
return
def read_parameters(self, file):
# try to locate the parameters file
try:
path = os.path.dirname(__file__)
ifile = os.path.join(path,'../'+file)
input = lib.open_file_for_reading(ifile)
except:
input = lib.open_file_for_reading(file)
for line in input:
self.parse_line(line)
return
def parse_line(self, line):
# first, remove comments
comment_pos = line.find('#')
if comment_pos != -1:
line = line[:comment_pos]
# split the line into words
words = line.split()
if len(words) == 0:
return
# parse the words
if len(words)==3 and words[0] in number_dictionaries:
self.parse_to_number_dictionary(words)
elif len(words)==2 and words[0] in string_lists:
self.parse_to_string_list(words)
elif len(words)==2 and words[0] in distances:
self.parse_distance(words)
elif len(words)==2 and words[0] in parameters:
self.parse_parameter(words)
elif len(words)==2 and words[0] in strings:
self.parse_string(words)
elif len(words)>2 and words[0] in list_dictionaries:
self.parse_to_list_dictionary(words)
elif words[0] in matrices+pair_wise_matrices:
self.parse_to_matrix(words)
elif len(words)==3 and words[0] in string_dictionaries:
self.parse_to_string_dictionary(words)
#print(words)
return
def parse_to_number_dictionary(self, words):
exec('self.%s[\'%s\'] = %s'%tuple(words))
return
def parse_to_string_dictionary(self, words):
exec('self.%s[\'%s\'] = \'%s\''%tuple(words))
return
def parse_to_list_dictionary(self, words):
exec('if not \'%s\' in self.%s.keys(): self.%s[\'%s\'] = []'%(words[1],words[0],words[0],words[1]))
for word in words[2:]:
exec('self.%s[\'%s\'].append(%s)'%(words[0],words[1],word))
return
def parse_to_string_list(self, words):
exec('self.%s.append(\'%s\')'%tuple(words))
return
def parse_to_matrix(self, words):
exec('self.%s.add(%s)'%(words[0],tuple(words[1:])))
return
def parse_distance(self, words):
# float check needed
exec('self.%s = %s'%tuple(words))
exec('self.%s_squared = pow(%s,2)'%tuple(words))
return
def parse_parameter(self, words):
exec('self.%s = %s'%tuple(words))
return
def parse_string(self, words):
#print('self.%s = \'%s\''%tuple(words))
exec('self.%s = \'%s\''%tuple(words))
return
def set_up_data_structures(self):
for key_word in number_dictionaries+list_dictionaries+string_dictionaries:
exec('self.%s = {}'%key_word)
for key_word in string_lists:
exec('self.%s = []'%key_word)
for key_word in strings:
exec('self.%s = \'\''%key_word)
for key_word in matrices:
exec('self.%s = Interaction_matrix(\'%s\')'%(key_word,key_word))
for key_word in pair_wise_matrices:
exec('self.%s =Pair_wise_matrix(\'%s\')'%(key_word,key_word))
return
def print_interaction_parameters(self):
print('--------------- Model pKa values ----------------------')
for k in self.model_pkas.keys():
print('%3s %8.2f'%(k,self.model_pkas[k]))
print('')
print('--------------- Interactions --------------------------')
agroups = ['COO', 'HIS', 'CYS', 'TYR', 'SER', 'N+', 'LYS', 'AMD', 'ARG', 'TRP', 'ROH', 'CG', 'C2N', 'N30', 'N31', 'N32', 'N33', 'NAR', 'OCO', 'NP1', 'OH', 'O3', 'CL', 'F', 'NAM', 'N1', 'O2', 'OP', 'SH']
lgroups = ['CG', 'C2N', 'N30', 'N31', 'N32', 'N33', 'NAR', 'OCO', 'NP1', 'OH', 'O3', 'CL', 'F', 'NAM', 'N1', 'O2', 'OP', 'SH']
map = {
'CG' :['ARG'],
'C2N':['ARG'],
'N30':['N+','LYS'],
'N31':['N+','LYS'],
'N32':['N+','LYS'],
'N33':['N+','LYS'] ,
'NAR':['HIS'],
'OCO':['COO'],
'OP' :[],#['TYR','SER'],
'SH' :['CYS'] ,
'NP1':[],
'OH' :['ROH'],
'O3' :[] ,
'CL' :[],
'F' :[],
'NAM':['AMD'],
'N1' :[],
'O2' :[]}
for g1 in agroups:
for g2 in lgroups:
interaction = '%3s %3s %1s %4s %4s'%(g1,g2,
self.interaction_matrix[g1][g2],
self.sidechain_cutoffs.get_value(g1,g2)[0],
self.sidechain_cutoffs.get_value(g1,g2)[1])
map_interaction = ''
if g2 in map:
for m in map[g2]:
map_interaction += '|%3s %3s %1s %4s %4s'%(g1,m,
self.interaction_matrix[g1][m],
self.sidechain_cutoffs.get_value(g1,m)[0],
self.sidechain_cutoffs.get_value(g1,m)[1])
if self.interaction_matrix[g1][m] != self.interaction_matrix[g1][g2]:
map_interaction += '* '
if self.sidechain_cutoffs.get_value(g1,m)[0] != self.sidechain_cutoffs.get_value(g1,g2)[0] or \
self.sidechain_cutoffs.get_value(g1,m)[1] != self.sidechain_cutoffs.get_value(g1,g2)[1]:
map_interaction += '! '
else:
map_interaction += ' '
if len(map[g2])==0 and (self.sidechain_cutoffs.get_value(g1,g2)[0] !=3 or self.sidechain_cutoffs.get_value(g1,g2)[1] != 4):
map_interaction += '? '
print(interaction,map_interaction )
if g1==g2:
break
print('-')
print('--------------- Exceptions ----------------------------')
print('COO-HIS',self.COO_HIS_exception)
print('OCO-HIS',self.OCO_HIS_exception)
print('CYS-HIS',self.CYS_HIS_exception)
print('CYS-CYS',self.CYS_CYS_exception)
print('--------------- Mapping -------------------------------')
print("""
Titratable:
CG ARG
C2N ARG
N30 N+/LYS
N31 N+/LYS
N32 N+/LYS
N33 N+/LYS
NAR HIS
OCO COO
OP TYR/SER?
SH CYS
Non-titratable:
NP1 AMD?
OH ROH
O3 ?
CL
F
NAM
N1
O2
""")
return
def print_interaction_parameters_latex(self):
# print('--------------- Model pKa values ----------------------')
# for k in self.model_pkas.keys():
# print('%3s %8.2f'%(k,self.model_pkas[k]))
# print('')
# print('--------------- Interactions --------------------------')
agroups = ['COO', 'HIS', 'CYS', 'TYR', 'SER', 'N+', 'LYS', 'AMD', 'ARG', 'TRP', 'ROH', 'CG', 'C2N', 'N30', 'N31', 'N32', 'N33', 'NAR', 'OCO', 'NP1', 'OH', 'O3', 'CL', 'F', 'NAM', 'N1', 'O2', 'OP', 'SH']
lgroups = ['CG', 'C2N', 'N30', 'N31', 'N32', 'N33', 'NAR', 'OCO', 'NP1', 'OH', 'O3', 'CL', 'F', 'NAM', 'N1', 'O2', 'OP', 'SH']
map = {
'CG' :['ARG'],
'C2N':['ARG'],
'N30':['N+','LYS'],
'N31':['N+','LYS'],
'N32':['N+','LYS'],
'N33':['N+','LYS'] ,
'NAR':['HIS'],
'OCO':['COO'],
'OP' :[],#['TYR','SER'],
'SH' :['CYS'] ,
'NP1':['AMD'],
'OH' :['ROH'],
'O3' :[] ,
'CL' :[],
'F' :[],
'NAM':[],
'N1' :[],
'O2' :[]}
s = """
\\begin{longtable}{lllll}
\\caption{Ligand interaction parameters. For interactions not listed, the default value of %s is applied.}
\\label{tab:ligand_interaction_parameters}\\\\
\\toprule
Group1 & Group2 & Interaction & c1 &c2 \\\\
\\midrule
\\endfirsthead
\\multicolumn{5}{l}{\\emph{continued from the previous page}}\\\\
\\toprule
Group1 & Group2 & Interaction & c1 &c2 \\\\
\\midrule
\\endhead
\\midrule
\\multicolumn{5}{r}{\\emph{continued on the next page}}\\\\
\\endfoot
\\bottomrule
\\endlastfoot
"""%(self.sidechain_cutoffs.default)
for g1 in agroups:
for g2 in lgroups:
if self.interaction_matrix[g1][g2]=='-':
continue
if self.sidechain_cutoffs.get_value(g1,g2)==self.sidechain_cutoffs.default:
continue
s+= '%3s & %3s & %1s & %4s & %4s\\\\ \n'%(g1,g2,
self.interaction_matrix[g1][g2],
self.sidechain_cutoffs.get_value(g1,g2)[0],
self.sidechain_cutoffs.get_value(g1,g2)[1])
if g1==g2:
break
s += ' \\end{longtable}\n'
print(s)
return
def print_interactions_latex(self):
agroups = ['COO', 'HIS', 'CYS', 'TYR', 'SER', 'N+', 'LYS', 'AMD', 'ARG', 'TRP', 'ROH', 'CG', 'C2N', 'N30', 'N31', 'N32', 'N33', 'NAR', 'OCO', 'NP1', 'OH', 'O3', 'CL', 'F', 'NAM', 'N1', 'O2', 'OP', 'SH']
lgroups = ['CG', 'C2N', 'N30', 'N31', 'N32', 'N33', 'NAR', 'OCO', 'NP1', 'OH', 'O3', 'CL', 'F', 'NAM', 'N1', 'O2', 'OP', 'SH']
s = """
\\begin{longtable}{%s}
\\caption{Ligand interaction parameters. For interactions not listed, the default value of %s is applied.}
\\label{tab:ligand_interaction_parameters}\\\\
\\toprule
Group1 & Group2 & Interaction & c1 &c2 \\\\
\\midrule
\\endfirsthead
\\multicolumn{5}{l}{\\emph{continued from the previous page}}\\\\
\\toprule
Group1 & Group2 & Interaction & c1 &c2 \\\\
\\midrule
\\endhead
\\midrule
\\multicolumn{5}{r}{\\emph{continued on the next page}}\\\\
\\endfoot
\\bottomrule
\\endlastfoot
"""%('l'*len(agroups),self.sidechain_cutoffs.default)
for g1 in agroups:
for g2 in agroups:
s+= '%3s & %3s & %1s & %4s & %4s\\\\ \n'%(g1,g2,
self.interaction_matrix[g1][g2],
self.sidechain_cutoffs.get_value(g1,g2)[0],
self.sidechain_cutoffs.get_value(g1,g2)[1])
if g1==g2:
break
s += ' \\end{longtable}\n'
print(s)
return
class Interaction_matrix:
def __init__(self, name):
self.name = name
self.ordered_keys = []
self.dictionary = {}
return
def add(self,words):
new_group = words[0]
self.ordered_keys.append(new_group)
if not new_group in self.dictionary.keys():
self.dictionary[new_group] = {}
for i in range(len(self.ordered_keys)):
group = self.ordered_keys[i]
if len(words)>i+1:
try:
exec('self.value = %s'%words[i+1])
except:
self.value = words[i+1]
self.dictionary[group][new_group] = self.value
self.dictionary[new_group][group] = self.value
return
def get_value(self, item1, item2):
try:
return self.dictionary[item1][item2]
except:
return None
def __getitem__(self, group):
if group not in self.dictionary.keys():
raise Exception('%s not found in interaction matrix %s'%(group,self.name))
return self.dictionary[group]
def keys(self):
return self.dictionary.keys()
def __str__(self):
s = ' '
for k1 in self.ordered_keys:
s+='%3s '%k1
s+='\n'
for k1 in self.ordered_keys:
s+='%3s '%k1
for k2 in self.ordered_keys:
s+='%3s '%self[k1][k2]
s+='\n'
return s
# ks = ['COO', 'SER', 'ARG', 'LYS', 'HIS', 'AMD', 'CYS', 'TRP','ROH','TYR','N+','CG', 'C2N', 'N30', 'N31', 'N32', 'N33', 'NAR', 'OCO', 'NP1', 'OH', 'O3', 'CL', 'F', 'NAM', 'N1', 'O2', 'OP', 'SH']
# p = ''
# n=0
# for i in range(len(ks)):
# for j in range(i,len(ks)):
# if not [0.0,0.0]==self[ks[i]][ks[j]]:
# if not [3.0,4.0]==self[ks[i]][ks[j]]:
# p+='sidechain_cutoff %3s %3s %s\n'%(ks[i],ks[j],self[ks[i]][ks[j]])
# n+=1
# print('total',n,len(ks))
# return p
class Pair_wise_matrix:
def __init__(self, name):
self.name = name
self.dictionary = {}
self.default = [0.0, 0.0]
return
def add(self,words):
# assign the default value
if len(words)==3 and words[0]=='default':
self.default = [float(words[1]), float(words[2])]
return
# assign non-default values
g1 = words[0]
g2 = words[1]
v = [float(words[2]), float(words[3])]
self.insert(g1,g2,v)
self.insert(g2,g1,v)
return
def insert(self, k1,k2,v):
if k1 in self.dictionary.keys() and k2 in self.dictionary[k1].keys():
if k1!=k2:
print ('Warning: Paramter value for %s, %s defined more than once'%(k1,k2))
if not k1 in self.dictionary:
self.dictionary[k1] = {}
self.dictionary[k1][k2] =v
return
def get_value(self, item1, item2):
try:
return self.dictionary[item1][item2]
except:
return self.default
def __getitem__(self, group):
if group not in self.dictionary.keys():
raise Exception('%s not found in interaction matrix %s'%(group,self.name))
return self.dictionary[group]
def keys(self):
return self.dictionary.keys()
def __str__(self):
s=''
for k1 in self.keys():
for k2 in self[k1].keys():
s += '%s %s %s\n'%(k1,k2,self[k1][k2])
return s

323
Source/pdb.py Normal file
View File

@@ -0,0 +1,323 @@
import string, sys, copy, Source.lib
from Source.atom import Atom
from Source.conformation_container import Conformation_container
expected_atom_numbers = {'ALA':5,
'ARG':11,
'ASN':8,
'ASP':8,
'CYS':6,
'GLY':4,
'GLN':9,
'GLU':9,
'HIS':10,
'ILE':8,
'LEU':8,
'LYS':9,
'MET':8,
'PHE':11,
'PRO':7,
'SER':6,
'THR':7,
'TRP':14,
'TYR':12,
'VAL':7}
def read_pdb(pdb_file, parameters, molecule):
conformations = {}
# read in all atoms in the file
lines = get_atom_lines_from_pdb(pdb_file, ignore_residues = parameters.ignore_residues, keep_protons = molecule.options.keep_protons, chains=molecule.options.chains)
for (name, atom) in lines:
if not name in conformations.keys():
conformations[name] = Conformation_container(name=name, parameters=parameters, molecular_container=molecule)
conformations[name].add_atom(atom)
# make a sorted list of conformation names
names = sorted(conformations.keys(), key=Source.lib.conformation_sorter)
return [conformations, names]
def protein_precheck(conformations, names):
for name in names:
atoms = conformations[name].atoms
res_ids = []
[res_ids.append(resid_from_atom(a)) for a in atoms if not res_ids.count(resid_from_atom(a))]
for res_id in res_ids:
res_atoms = [a for a in atoms if resid_from_atom(a) == res_id and a.element != 'H']
resname = res_atoms[0].resName
residue_label = '%3s%5s'%(resname, res_id)
# ignore ligand residues
if resname not in expected_atom_numbers:
continue
# check for c-terminal
if 'C-' in [a.terminal for a in res_atoms]:
if len(res_atoms) != expected_atom_numbers[resname]+1:
print('Warning: Unexpected number (%d) of atoms in residue %s in conformation %s'%(len(res_atoms),residue_label, name))
continue
# check number of atoms in residue
if len(res_atoms) != expected_atom_numbers[resname]:
print('Warning: Unexpected number (%d) of atoms in residue %s in conformation %s'%(len(res_atoms),residue_label, name))
return
def resid_from_atom(a):
return '%4d %s %s'%(a.resNumb,a.chainID,a.icode)
def get_atom_lines_from_pdb(pdb_file, ignore_residues = [], keep_protons=False, tags = ['ATOM ', 'HETATM'], chains=None):
lines = Source.lib.open_file_for_reading(pdb_file).readlines()
nterm_residue = 'next_residue'
old_residue = None
terminal = None
model = 1
for line in lines:
tag = line[0:6]
# set the model number
if tag == 'MODEL ':
model = int(line[6:])
nterm_residue = 'next_residue'
if tag == 'TER ':
nterm_residue = 'next_residue'
if tag in tags:
alt_conf_tag = line[16]
residue_name = line[12:16]
residue_number = line[22:26]
# check if we want this residue
if line[17:20] in ignore_residues:
continue
if chains and line[21] not in chains:
continue
# set the Nterm residue number - nessecary because we may need to
# identify more than one N+ group for structures with alt_conf tags
if nterm_residue == 'next_residue' and tag == 'ATOM ':
# make sure that we reached a new residue - nessecary if OXT is not the last atom in
# the previous residue
if old_residue != residue_number:
nterm_residue = residue_number
old_residue = None
# Identify the configuration
# convert digits to letters
if alt_conf_tag in '123456789':
alt_conf_tag = chr(ord(alt_conf_tag)+16)
if alt_conf_tag == ' ':
alt_conf_tag = 'A'
conformation = '%d%s'%(model, alt_conf_tag)
# set the terminal
if tag == 'ATOM ':
if residue_name.strip() == 'N' and nterm_residue == residue_number:
terminal = 'N+'
if residue_name.strip() in ['OXT','O\'\'']:
terminal = 'C-'
nterm_residue = 'next_residue'
old_residue = residue_number
# and yield the atom
atom = Atom(line=line)
atom.terminal = terminal
#if keep_protons:
# atom.is_protonated = True
if not (atom.element == 'H' and not keep_protons): #ignore hydrogen
yield (conformation, atom)
terminal = None
return
def write_pdb(conformation, filename):
write_pdb_for_atoms(conformation.atoms, filename)
return
def write_pdb_for_atoms(atoms, filename, make_conect_section=False):
out = Source.lib.open_file_for_writing(filename)
for atom in atoms:
out.write(atom.make_pdb_line())
if make_conect_section:
for atom in atoms:
out.write(atom.make_conect_line())
out.close()
return
def write_mol2_for_atoms(atoms, filename):
header = '@<TRIPOS>MOLECULE\n\n%d %d\nSMALL\nUSER_CHARGES\n'
atoms_section = '@<TRIPOS>ATOM\n'
for i in range(len(atoms)):
atoms_section += atoms[i].make_mol2_line(i+1)
bonds_section = '@<TRIPOS>BOND\n'
id = 1
for i in range(len(atoms)):
for j in range(i+1,len(atoms)):
if atoms[i] in atoms[j].bonded_atoms:
type = get_bond_order(atoms[i],atoms[j])
bonds_section += '%7d %7d %7d %7s\n'%(id, i+1, j+1, type)
id+=1
substructure_section = '@<TRIPOS>SUBSTRUCTURE\n\n'
if len(atoms)>0:
substructure_section = '@<TRIPOS>SUBSTRUCTURE\n%-7d %10s %7d\n'%(atoms[0].resNumb,atoms[0].resName,atoms[0].numb)
out = Source.lib.open_file_for_writing(filename)
out.write(header%(len(atoms),id-1))
out.write(atoms_section)
out.write(bonds_section)
out.write(substructure_section)
out.close()
return
def get_bond_order(atom1, atom2):
type = '1'
pi_electrons1 = atom1.number_of_pi_electrons_in_double_and_triple_bonds
pi_electrons2 = atom2.number_of_pi_electrons_in_double_and_triple_bonds
if '.ar' in atom1.sybyl_type:
pi_electrons1 -=1
if '.ar' in atom2.sybyl_type:
pi_electrons2 -=1
if pi_electrons1 > 0 and pi_electrons2 > 0:
type = '%d'%(min(pi_electrons1, pi_electrons2)+1)
if '.ar' in atom1.sybyl_type and '.ar' in atom2.sybyl_type:
type = 'ar'
return type
def write_input(molecular_container, filename):
out = Source.lib.open_file_for_writing(filename)
for conformation_name in molecular_container.conformation_names:
out.write('MODEL %s\n'%conformation_name)
# write atoms
for atom in molecular_container.conformations[conformation_name].atoms:
out.write(atom.make_input_line())
# write bonds
for atom in molecular_container.conformations[conformation_name].atoms:
out.write(atom.make_conect_line())
# write covalently coupled groups
for group in molecular_container.conformations[conformation_name].groups:
out.write(group.make_covalently_coupled_line())
# write non-covalently coupled groups
for group in molecular_container.conformations[conformation_name].groups:
out.write(group.make_non_covalently_coupled_line())
out.write('ENDMDL\n')
out.close()
return
def read_input(input_file, parameters,molecule):
conformations = {}
# read in all atoms in the input file
lines = get_atom_lines_from_input(input_file)
for (name, atom) in lines:
if not name in conformations.keys():
conformations[name] = Conformation_container(name=name, parameters=parameters, molecular_container=molecule)
conformations[name].add_atom(atom)
# make a sorted list of conformation names
names = sorted(conformations.keys(), key=Source.lib.conformation_sorter)
return [conformations, names]
def get_atom_lines_from_input(input_file, tags = ['ATOM ','HETATM']):
lines = Source.lib.open_file_for_reading(input_file).readlines()
conformation = ''
atoms = {}
numbers = []
for line in lines:
tag = line[0:6]
# set the conformation
if tag == 'MODEL ':
conformation = line[6:].strip()
# found an atom - save it
if tag in tags:
atom = Atom(line=line)
atom.get_input_parameters()
atom.groups_extracted = 1
atom.is_protonated = True
atoms[atom.numb] = atom
numbers.append(atom.numb)
# found bonding information - apply it
if tag == 'CONECT' and len(line)>14:
conect_numbers = [line[i:i+5] for i in range(6, len(line)-1, 5)]
center_atom = atoms[int(conect_numbers[0])]
for n in conect_numbers[1:]:
b = atoms[int(n)]
# remember to check for cysteine bridges
if center_atom.element == 'S' and b.element == 'S':
center_atom.cysteine_bridge = True
b.cysteine_bridge = True
# set up bonding
if not b in center_atom.bonded_atoms:
center_atom.bonded_atoms.append(b)
if not center_atom in b.bonded_atoms:
b.bonded_atoms.append(center_atom)
# found info on covalent coupling
if tag == 'CCOUPL' and len(line)>14:
conect_numbers = [line[i:i+5] for i in range(6, len(line)-1, 5)]
center_atom = atoms[int(conect_numbers[0])]
for n in conect_numbers[1:]:
cg = atoms[int(n)]
center_atom.group.couple_covalently(cg.group)
# found info on non-covalent coupling
if tag == 'NCOUPL' and len(line)>14:
conect_numbers = [line[i:i+5] for i in range(6, len(line)-1, 5)]
center_atom = atoms[int(conect_numbers[0])]
for n in conect_numbers[1:]:
cg = atoms[int(n)]
center_atom.group.couple_non_covalently(cg.group)
# this conformation is done - yield the atoms
if tag == 'ENDMDL':
for n in numbers:
yield (conformation, atoms[n])
# prepare for next conformation
atoms = {}
numbers = []
return

568
Source/protein_bonds.dat Normal file
View File

@@ -0,0 +1,568 @@
(dp0
S'CYS'
p1
(dp2
S'CB'
p3
(lp4
S'CA'
p5
aS'SG'
p6
asg5
(lp7
g3
asg6
(lp8
g3
aS'SG'
p9
assS'GLN'
p10
(dp11
S'CB'
p12
(lp13
S'CA'
p14
aS'CG'
p15
asg14
(lp16
g12
asg15
(lp17
g12
aS'CD'
p18
asg18
(lp19
g15
aS'OE1'
p20
aS'NE2'
p21
asg21
(lp22
g18
asg20
(lp23
g18
assS'HIS'
p24
(dp25
S'CD2'
p26
(lp27
S'CG'
p28
aS'NE2'
p29
asS'CB'
p30
(lp31
S'CA'
p32
ag28
asg32
(lp33
g30
asg28
(lp34
g30
aS'ND1'
p35
ag26
asS'CE1'
p36
(lp37
g35
ag29
asg35
(lp38
g28
ag36
asg29
(lp39
g26
ag36
assS'ASN'
p40
(dp41
S'CB'
p42
(lp43
S'CA'
p44
aS'CG'
p45
asg44
(lp46
g42
asS'ND2'
p47
(lp48
g45
asg45
(lp49
g42
aS'OD1'
p50
ag47
asg50
(lp51
g45
assS'VAL'
p52
(dp53
S'CG1'
p54
(lp55
S'CB'
p56
asg56
(lp57
S'CA'
p58
ag54
aS'CG2'
p59
asg58
(lp60
g56
asg59
(lp61
g56
assS'LYS'
p62
(dp63
S'CB'
p64
(lp65
S'CA'
p66
aS'CG'
p67
asg66
(lp68
g64
asg67
(lp69
g64
aS'CD'
p70
asS'CE'
p71
(lp72
g70
aS'NZ'
p73
asg70
(lp74
g67
ag71
asg73
(lp75
g71
assS'ILE'
p76
(dp77
S'CG1'
p78
(lp79
S'CB'
p80
aS'CD1'
p81
asg80
(lp82
S'CA'
p83
ag78
aS'CG2'
p84
asg83
(lp85
g80
asg84
(lp86
g80
asg81
(lp87
g78
assS'PRO'
p88
(dp89
S'CB'
p90
(lp91
S'CA'
p92
aS'CG'
p93
asg92
(lp94
g90
asS'CD'
p95
(lp96
S'N'
p97
ag93
asg93
(lp98
g90
ag95
asg97
(lp99
g95
assS'THR'
p100
(dp101
S'CB'
p102
(lp103
S'CA'
p104
aS'OG1'
p105
aS'CG2'
p106
asg104
(lp107
g102
asg106
(lp108
g102
asg105
(lp109
g102
assS'PHE'
p110
(dp111
S'CD2'
p112
(lp113
S'CG'
p114
aS'CE2'
p115
asS'CB'
p116
(lp117
S'CA'
p118
ag114
asg118
(lp119
g116
asg114
(lp120
g116
aS'CD1'
p121
ag112
asS'CZ'
p122
(lp123
S'CE1'
p124
ag115
asg121
(lp125
g114
ag124
asg124
(lp126
g121
ag122
asg115
(lp127
g112
ag122
assS'ALA'
p128
(dp129
S'CB'
p130
(lp131
S'CA'
p132
asg132
(lp133
g130
assS'MET'
p134
(dp135
S'CB'
p136
(lp137
S'CA'
p138
aS'CG'
p139
asg138
(lp140
g136
asg139
(lp141
g136
aS'SD'
p142
asS'CE'
p143
(lp144
g142
asg142
(lp145
g139
ag143
assS'ASP'
p146
(dp147
S'CB'
p148
(lp149
S'CA'
p150
aS'CG'
p151
asg150
(lp152
g148
asg151
(lp153
g148
aS'OD1'
p154
aS'OD2'
p155
asg155
(lp156
g151
asg154
(lp157
g151
assS'LEU'
p158
(dp159
S'CB'
p160
(lp161
S'CA'
p162
aS'CG'
p163
asg162
(lp164
g160
asg163
(lp165
g160
aS'CD1'
p166
aS'CD2'
p167
asg166
(lp168
g163
asg167
(lp169
g163
assS'ARG'
p170
(dp171
S'CB'
p172
(lp173
S'CA'
p174
aS'CG'
p175
asg174
(lp176
g172
asg175
(lp177
g172
aS'CD'
p178
asS'NE'
p179
(lp180
g178
aS'CZ'
p181
asg178
(lp182
g175
ag179
asg181
(lp183
g179
aS'NH1'
p184
aS'NH2'
p185
asg184
(lp186
g181
asg185
(lp187
g181
assS'TRP'
p188
(dp189
S'CZ2'
p190
(lp191
S'CE2'
p192
aS'CH2'
p193
asS'CB'
p194
(lp195
S'CA'
p196
aS'CG'
p197
asg196
(lp198
g194
asg197
(lp199
g194
aS'CD1'
p200
aS'CD2'
p201
asg193
(lp202
g190
aS'CZ3'
p203
asg192
(lp204
g201
aS'NE1'
p205
ag190
asS'CE3'
p206
(lp207
g201
ag203
asg200
(lp208
g197
ag205
asg201
(lp209
g197
ag192
ag206
asg203
(lp210
g206
ag193
asg205
(lp211
g200
ag192
assS'GLU'
p212
(dp213
S'OE2'
p214
(lp215
S'CD'
p216
asS'CA'
p217
(lp218
S'CB'
p219
asS'CG'
p220
(lp221
g219
ag216
asg216
(lp222
g220
aS'OE1'
p223
ag214
asg219
(lp224
g217
ag220
asg223
(lp225
g216
assS'TYR'
p226
(dp227
S'CD2'
p228
(lp229
S'CG'
p230
aS'CE2'
p231
asS'OH'
p232
(lp233
S'CZ'
p234
asS'CB'
p235
(lp236
S'CA'
p237
ag230
asg237
(lp238
g235
asg230
(lp239
g235
aS'CD1'
p240
ag228
asg234
(lp241
S'CE1'
p242
ag231
ag232
asg240
(lp243
g230
ag242
asg242
(lp244
g240
ag234
asg231
(lp245
g228
ag234
assS'SER'
p246
(dp247
S'OG'
p248
(lp249
S'CB'
p250
asg250
(lp251
S'CA'
p252
ag248
asg252
(lp253
g250
ass.

443
Source/protonate.py Executable file
View File

@@ -0,0 +1,443 @@
#!/usr/bin/python
from Source.vector_algebra import *
import Source.bonds, Source.pdb, Source.atom
class Protonate:
""" Protonates atoms using VSEPR theory """
def __init__(self, verbose=False):
self.verbose=verbose
self.valence_electrons = {'H': 1,
'He':2,
'Li':1,
'Be':2,
'B': 3,
'C': 4,
'N': 5,
'O': 6,
'F': 7,
'Ne':8,
'Na':1,
'Mg':2,
'Al':3,
'Si':4,
'P': 5,
'S': 6,
'Cl':7,
'Ar':8,
'K': 1,
'Ca':2,
'Sc':2,
'Ti':2,
'Va':2,
'Cr':1,
'Mn':2,
'Fe':2,
'Co':2,
'Ni':2,
'Cu':1,
'Zn':2,
'Ga':3,
'Ge':4,
'As':5,
'Se':6,
'Br':7,
'Kr':8}
self.standard_charges= {'ARG-NH1':1.0,
'ASP-OD2':-1.0,
'GLU-OE2':-1.0,
'HIS-ND1':1.0,
'LYS-NZ':1.0,
'N+':1.0,
'C-':-1.0}
self.sybyl_charges = {'N.pl3':+1,
'N.3':+1,
'N.4':+1,
'N.ar':+1,
'O.co2-':-1}
self.bond_lengths = {'C':1.09,
'N':1.01,
'O':0.96,
'F':0.92,
'Cl':1.27,
'Br':1.41,
'I':1.61,
'S':1.35}
# protonation_methods[steric_number] = method
self.protonation_methods = {4:self.tetrahedral,
3:self.trigonal}
return
def protonate(self, molecules):
""" Will protonate all atoms in the molecular container """
self.display('----- Protonation started -----')
# Remove all currently present hydrogen atoms
self.remove_all_hydrogen_atoms(molecules)
# protonate all atoms
for name in molecules.conformation_names:
non_H_atoms = molecules.conformations[name].get_non_hydrogen_atoms()
for atom in non_H_atoms:
self.protonate_atom(atom)
# fix hydrogen names
#self.set_proton_names(non_H_atoms)
return
def remove_all_hydrogen_atoms(self, molecular_container):
for name in molecular_container.conformation_names:
molecular_container.conformations[name].atoms = molecular_container.conformations[name].get_non_hydrogen_atoms()
return
def set_charge(self, atom):
# atom is a protein atom
if atom.type=='atom':
key = '%3s-%s'%(atom.resName, atom.name)
if atom.terminal:
self.display(atom.terminal)
key=atom.terminal
if key in list(self.standard_charges.keys()):
atom.charge = self.standard_charges[key]
self.display('Charge', atom, atom.charge)
atom.charge_set = True
# atom is a ligand atom
elif atom.type=='hetatm':
if atom.sybyl_type in list(self.sybyl_charges.keys()):
atom.charge = self.sybyl_charges[atom.sybyl_type]
atom.sybyl_type = atom.sybyl_type.replace('-','')
atom.charge_set = True
return
def protonate_atom(self, atom):
if atom.is_protonated: return
if atom.element == 'H': return
self.set_charge(atom)
self.set_number_of_protons_to_add(atom)
self.set_steric_number_and_lone_pairs(atom)
self.add_protons(atom)
atom.is_protonated = True
return
def set_proton_names(self, heavy_atoms):
for heavy_atom in heavy_atoms:
i = 1
for bonded in heavy_atom.bonded_atoms:
if bonded.element == 'H':
bonded.name+='%d'%i
i+=1
return
def set_number_of_protons_to_add(self, atom):
self.display('*'*10)
self.display('Setting number of protons to add for',atom)
atom.number_of_protons_to_add = 8
self.display(' %4d'%8)
atom.number_of_protons_to_add -= self.valence_electrons[atom.element]
self.display('Valence eletrons: %4d'%-self.valence_electrons[atom.element])
atom.number_of_protons_to_add -= len(atom.bonded_atoms)
self.display('Number of bonds: %4d'%- len(atom.bonded_atoms))
atom.number_of_protons_to_add -= atom.number_of_pi_electrons_in_double_and_triple_bonds
self.display('Pi electrons: %4d'%-atom.number_of_pi_electrons_in_double_and_triple_bonds)
atom.number_of_protons_to_add += int(atom.charge)
self.display('Charge: %4.1f'%atom.charge)
self.display('-'*10)
self.display(atom.number_of_protons_to_add)
return
def set_steric_number_and_lone_pairs(self, atom):
# If we already did this, there is no reason to do it again
if atom.steric_number_and_lone_pairs_set:
return
self.display('='*10)
self.display('Setting steric number and lone pairs for',atom)
# costumly set the N backbone atoms up for peptide bond trigonal planer shape
#if atom.name == 'N' and len(atom.bonded_atoms) == 2:
# atom.steric_number = 3
# atom.number_of_lone_pairs = 0
# self.display 'Peptide bond: steric number is %d and number of lone pairs is %s'%(atom.steric_number,
# atom.number_of_lone_pairs)
# return
atom.steric_number = 0
self.display('%65s: %4d'%('Valence electrons',self.valence_electrons[atom.element]))
atom.steric_number += self.valence_electrons[atom.element]
self.display('%65s: %4d'%('Number of bonds',len(atom.bonded_atoms)))
atom.steric_number += len(atom.bonded_atoms)
self.display('%65s: %4d'%('Number of hydrogen atoms to add',atom.number_of_protons_to_add))
atom.steric_number += atom.number_of_protons_to_add
self.display('%65s: %4d'%('Number of pi-electrons in double and triple bonds(-)',atom.number_of_pi_electrons_in_double_and_triple_bonds))
atom.steric_number -= atom.number_of_pi_electrons_in_double_and_triple_bonds
self.display('%65s: %4d'%('Number of pi-electrons in conjugated double and triple bonds(-)',atom.number_of_pi_electrons_in_conjugate_double_and_triple_bonds))
atom.steric_number -= atom.number_of_pi_electrons_in_conjugate_double_and_triple_bonds
self.display('%65s: %4d'%('Number of donated co-ordinated bonds',0))
atom.steric_number += 0
self.display('%65s: %4.1f'%('Charge(-)',atom.charge))
atom.steric_number -= atom.charge
atom.steric_number = math.floor(atom.steric_number/2.0)
atom.number_of_lone_pairs = atom.steric_number - len(atom.bonded_atoms) - atom.number_of_protons_to_add
self.display('-'*70)
self.display('%65s: %4d'%('Steric number',atom.steric_number))
self.display('%65s: %4d'%('Number of lone pairs',atom.number_of_lone_pairs))
atom.steric_number_and_lone_pairs_set = True
return
def add_protons(self, atom):
# decide which method to use
self.display('PROTONATING',atom)
if atom.steric_number in list(self.protonation_methods.keys()):
self.protonation_methods[atom.steric_number](atom)
else:
print('Warning: Do not have a method for protonating',atom,'(steric number: %d)'%atom.steric_number)
return
def trigonal(self, atom):
self.display('TRIGONAL - %d bonded atoms'%(len(atom.bonded_atoms)))
rot_angle = math.radians(120.0)
c = vector(atom1 = atom)
# 0 bonds
if len(atom.bonded_atoms) == 0:
pass
# 1 bond
if len(atom.bonded_atoms) == 1 and atom.number_of_protons_to_add > 0:
# Add another atom with the right angle to the first one
a = vector(atom1 = atom, atom2 = atom.bonded_atoms[0])
# use plane of bonded trigonal atom - e.g. arg
self.set_steric_number_and_lone_pairs(atom.bonded_atoms[0])
if atom.bonded_atoms[0].steric_number == 3 and len(atom.bonded_atoms[0].bonded_atoms)>1:
# use other atoms bonded to the neighbour to establish the plane, if possible
other_atom_indices = []
for i in range(len(atom.bonded_atoms[0].bonded_atoms)):
if atom.bonded_atoms[0].bonded_atoms[i] != atom:
other_atom_indices.append(i)
v1 = vector(atom1 = atom, atom2 = atom.bonded_atoms[0])
v2 = vector(atom1 = atom.bonded_atoms[0],
atom2 = atom.bonded_atoms[0].bonded_atoms[other_atom_indices[0]])
axis = v1**v2
# this is a trick to make sure that the order of atoms doesn't influence
# the final postions of added protons
if len(other_atom_indices)>1:
v3 = vector(atom1 = atom.bonded_atoms[0],
atom2 = atom.bonded_atoms[0].bonded_atoms[other_atom_indices[1]])
axis2 = v1**v3
if axis * axis2>0:
axis = axis+axis2
else:
axis = axis-axis2
else:
axis = a.orthogonal()
a = rotate_vector_around_an_axis(rot_angle, axis, a)
a = self.set_bond_distance(a, atom.element)
self.add_proton(atom, c+a)
# 2 bonds
if len(atom.bonded_atoms) == 2 and atom.number_of_protons_to_add > 0:
# Add another atom with the right angle to the first two
a1 = vector(atom1 = atom, atom2 = atom.bonded_atoms[0]).rescale(1.0)
a2 = vector(atom1 = atom, atom2 = atom.bonded_atoms[1]).rescale(1.0)
new_a = -a1 - a2
new_a = self.set_bond_distance(new_a, atom.element)
self.add_proton(atom, c+new_a)
return
def tetrahedral(self, atom):
self.display('TETRAHEDRAL - %d bonded atoms'%(len(atom.bonded_atoms)))
rot_angle = math.radians(109.5)
# sanity check
# if atom.number_of_protons_to_add + len(atom.bonded_atoms) != 4:
# self.display 'Error: Attempting tetrahedral structure with %d bonds'%(atom.number_of_protons_to_add +
# len(atom.bonded_atoms))
c = vector(atom1 = atom)
# 0 bonds
if len(atom.bonded_atoms) == 0:
pass
# 1 bond
if len(atom.bonded_atoms) == 1 and atom.number_of_protons_to_add > 0:
# Add another atom with the right angle to the first one
a = vector(atom1 = atom, atom2 = atom.bonded_atoms[0])
axis = a.orthogonal()
a = rotate_vector_around_an_axis(rot_angle, axis, a)
a = self.set_bond_distance(a, atom.element)
self.add_proton(atom, c+a)
# 2 bonds
if len(atom.bonded_atoms) == 2 and atom.number_of_protons_to_add > 0:
# Add another atom with the right angle to the first two
a1 = vector(atom1 = atom, atom2 = atom.bonded_atoms[0]).rescale(1.0)
a2 = vector(atom1 = atom, atom2 = atom.bonded_atoms[1]).rescale(1.0)
axis = a1 + a2
new_a = rotate_vector_around_an_axis(math.radians(90), axis, -a1)
new_a = self.set_bond_distance(new_a, atom.element)
self.add_proton(atom, c+new_a)
# 3 bonds
if len(atom.bonded_atoms) == 3 and atom.number_of_protons_to_add > 0:
a1 = vector(atom1 = atom, atom2 = atom.bonded_atoms[0]).rescale(1.0)
a2 = vector(atom1 = atom, atom2 = atom.bonded_atoms[1]).rescale(1.0)
a3 = vector(atom1 = atom, atom2 = atom.bonded_atoms[2]).rescale(1.0)
new_a = -a1-a2-a3
new_a = self.set_bond_distance(new_a, atom.element)
self.add_proton(atom, c+new_a)
return
def add_proton(self, atom, position):
# Create the new proton
new_H = Source.atom.Atom()
new_H.setProperty(numb = None,
name = 'H%s'%atom.name[1:],
resName = atom.resName,
chainID = atom.chainID,
resNumb = atom.resNumb,
x = round(position.x,3), # round of to three digimal points
y = round(position.y,3), # to avoid round-off differences
z = round(position.z,3), # when input file
occ = None,
beta = None)
new_H.element = 'H'
new_H.type = atom.type
new_H.bonded_atoms = [atom]
new_H.charge = 0
new_H.steric_number = 0
new_H.number_of_lone_pairs = 0
new_H.number_of_protons_to_add = 0
new_H.number_of_pi_electrons_in_double_and_triple_bonds = 0
new_H.is_protonates = True
atom.bonded_atoms.append(new_H)
atom.number_of_protons_to_add -=1
atom.conformation_container.add_atom(new_H)
# update names of all protons on this atom
new_H.residue_label = "%-3s%4d%2s" % (new_H.name,new_H.resNumb, new_H.chainID)
no_protons = atom.count_bonded_elements('H')
if no_protons > 1:
i = 1
for proton in atom.get_bonded_elements('H'):
proton.name = 'H%s%d'%(atom.name[1:],i)
proton.residue_label = "%-3s%4d%2s" % (proton.name,proton.resNumb, proton.chainID)
i+=1
self.display('added',new_H, 'to',atom)
return
def set_bond_distance(self, a, element):
d = 1.0
if element in list(self.bond_lengths.keys()):
d = self.bond_lengths[element]
else:
print('WARNING: Bond length for %s not found, using the standard value of %f'%(element, d))
a = a.rescale(d)
return a
def display(self,*text):
if self.verbose:
s = ''
for t in text:
s+='%s '%t
print(s)
return
if __name__ == '__main__':
import protein, pdb, sys,os
arguments = sys.argv
if len(arguments) != 2:
print('Usage: protonate.py <pdb_file>')
sys.exit(0)
filename = arguments[1]
if not os.path.isfile(filename):
print('Error: Could not find \"%s\"'%filename)
sys.exit(1)
p = Protonate()
pdblist = pdb.readPDB(filename)
my_protein = protein.Protein(pdblist,'test.pdb')
p.remove_all_hydrogen_atoms_from_protein(my_protein)
my_protein.writePDB('before_protonation.pdb')
p.protonate_protein(my_protein)
## write out protonated file
my_protein.writePDB('protonated.pdb')

315
Source/vector_algebra.py Normal file
View File

@@ -0,0 +1,315 @@
import math
class vector:
""" Vector """
def __init__(self, xi=0.0, yi=0.0, zi=0.0, atom1 = 0, atom2 = 0):
self.x = xi
self.y = yi
self.z = zi
if atom1:
# make vector pointing to atom1
self.x = atom1.x
self.y = atom1.y
self.z = atom1.z
if atom2:
# make inter-atomic vector (atom1 -> atom2)
self.x = atom2.x - self.x
self.y = atom2.y - self.y
self.z = atom2.z - self.z
return
def __add__(self, other):
return vector(self.x + other.x,
self.y + other.y,
self.z + other.z)
def __sub__(self, other):
return vector(self.x - other.x,
self.y - other.y,
self.z - other.z)
def __mul__(self, other):
""" Dot product, scalar and matrix multiplication """
if isinstance(other,vector):
return self.x * other.x + self.y * other.y + self.z * other.z
elif isinstance(other, matrix4x4):
return vector(
xi = other.a11*self.x + other.a12*self.y + other.a13*self.z + other.a14*1.0,
yi = other.a21*self.x + other.a22*self.y + other.a23*self.z + other.a24*1.0,
zi = other.a31*self.x + other.a32*self.y + other.a33*self.z + other.a34*1.0
)
elif type(other) in [int, float]:
return vector(self.x * other, self.y * other, self.z * other)
else:
print('%s not supported'%type(other))
raise TypeError
def __rmul__(self,other):
return self.__mul__(other)
def __pow__(self, other):
""" Cross product """
return vector(self.y * other.z - self.z * other.y,
self.z * other.x - self.x * other.z,
self.x * other.y - self.y * other.x)
def __neg__(self):
res = vector(xi = -self.x,
yi = -self.y,
zi = -self.z)
return res
def sq_length(self):
return self.x * self.x + self.y * self.y + self.z * self.z
def length(self):
return math.sqrt(self.sq_length())
def __str__(self):
return '%10.4f %10.4f %10.4f'%(self.x, self.y, self.z)
def __repr__(self):
return '<vector>'
def orthogonal(self):
""" Returns a vector orthogonal to self """
res = vector(self.y, -self.x, 0)
if abs(self.y) < abs(self.z):
res = vector(self.z, 0, -self.x)
return res
def rescale(self, new_length):
""" Rescale vector to new length while preserving direction """
frac = new_length/(self.length())
res = vector(xi = self.x*frac,
yi = self.y*frac,
zi = self.z*frac)
return res
class matrix4x4:
def __init__(self,
a11i=0.0, a12i=0.0, a13i=0.0, a14i=0.0,
a21i=0.0, a22i=0.0, a23i=0.0, a24i=0.0,
a31i=0.0, a32i=0.0, a33i=0.0, a34i=0.0,
a41i=0.0, a42i=0.0, a43i=0.0, a44i=0.0):
self.a11 = a11i
self.a12 = a12i
self.a13 = a13i
self.a14 = a14i
self.a21 = a21i
self.a22 = a22i
self.a23 = a23i
self.a24 = a24i
self.a31 = a31i
self.a32 = a32i
self.a33 = a33i
self.a34 = a34i
self.a41 = a41i
self.a42 = a42i
self.a43 = a43i
self.a44 = a44i
return
# methods working on vectors
def angle(a, b):
dot = a * b
return math.acos(dot / (a.length() * b.length()))
def angle_degrees(a,b):
return math.degrees(angle(a, b))
def signed_angle_around_axis(a,b, axis):
na = a**axis
nb = b**axis
v = angle(na,nb)
d = b*(a**axis)
if d < 0:
v =-v
return v
def signed_angle_degrees(a,b):
return 180/math.pi * signed_angle(a, b)
def rotate_vector_around_an_axis(theta, axis, v):
#print "# 1. rotate space about the z-axis so that the rotation axis lies in the xz-plane"
gamma = 0.0
if axis.y != 0:
if axis.x != 0:
gamma = -axis.x/abs(axis.x)*math.asin(axis.y/(math.sqrt(axis.x*axis.x + axis.y*axis.y)))
else:
gamma = math.pi/2.0
Rz = rotate_atoms_around_z_axis(gamma)
v = Rz * v
axis = Rz * axis
#print "# 2. rotate space about the y-axis so that the rotation axis lies along the z-axis"
beta = 0.0
if axis.x != 0:
beta = -axis.x/abs(axis.x)*math.acos(axis.z/math.sqrt(axis.x*axis.x + axis.z*axis.z))
Ry = rotate_atoms_around_y_axis(beta)
v = Ry * v
axis = Ry *axis
#print "# 3. perform the desired rotation by theta about the z-axis"
Rz = rotate_atoms_around_z_axis(theta)
v = Rz * v
#print "# 4. apply the inverse of step 2."
Ry = rotate_atoms_around_y_axis(-beta)
v = Ry * v
#print "# 5. apply the inverse of step 1."
Rz = rotate_atoms_around_z_axis(-gamma)
v = Rz * v
return v
def rotate_atoms_around_z_axis(angle):
Rz = matrix4x4(
a11i = math.cos(angle), a12i = -math.sin(angle), a13i = 0.0, a14i = 0.0,
a21i = math.sin(angle), a22i = math.cos(angle), a23i = 0.0, a24i = 0.0,
a31i = 0.0 , a32i = 0.0 , a33i = 1.0, a34i = 0.0,
a41i = 0.0 , a42i = 0.0 , a43i = 0.0, a44i = 1.0
)
return Rz
def rotate_atoms_around_y_axis(angle):
Ry = matrix4x4(
a11i = math.cos(angle), a12i = 0.0, a13i = math.sin(angle), a14i = 0.0,
a21i = 0.0 , a22i = 1.0, a23i = 0.0 , a24i = 0.0,
a31i = -math.sin(angle), a32i = 0.0, a33i = math.cos(angle), a34i = 0.0,
a41i = 0.0 , a42i = 0.0, a43i = 0.0 , a44i = 1.0
)
return Ry
class multi_vector:
def __init__(self, atom1=0, atom2=0):
self.vectors = []
self.keys = []
# store vectors for all configurations of atoms
if atom1!=0:
self.keys = lib.get_sorted_configurations(atom1.configurations.keys())
if atom2!=0:
keys2 = lib.get_sorted_configurations(atom2.configurations.keys())
if self.keys != keys2:
raise 'Cannot make multi vector: Atomic configurations mismatch for\n %s\n %s\n'%(atom1,atom2)
for key in self.keys:
atom1.setConfiguration(key)
if atom2!=0:
atom2.setConfiguration(key)
v = vector(atom1=atom1, atom2=atom2)
self.vectors.append(v)
#print(key,v)
return
def __getattribute__(self,name):
try:
return object.__getattribute__(self, name)
except AttributeError:
return self.do_job(name)
def __str__(self):
res = ''
for i in range(len(self.keys)):
res += '%s %s\n'%(self.keys[i], self.vectors[i])
return res
def do_job(self, job):
#print(job)
self.res = multi_vector()
for i in range(len(self.vectors)):
self.res.vectors.append(eval('self.vectors[%d].%s()'%(i,job)))
self.res.keys.append(self.keys[i])
return self.get_result
def get_result(self):
return self.res
def generic_operation(self, operation, other):
if self.keys != other.keys:
raise 'Incompatable keys'
self.res = multi_vector()
for i in range(len(self.vectors)):
self.res.vectors.append(eval('self.vectors[%d] %s other.vectors[%d]'%(i,operation,i)))
self.res.keys.append(self.keys[i])
return
def __add__(self, other):
self.generic_operation('+',other)
return self.res
def __sub__(self, other):
self.generic_operation('-',other)
return self.res
def __mul__(self, other):
self.generic_operation('*',other)
return self.res
def __pow__(self, other):
self.generic_operation('**',other)
return self.res
def generic_self_operation(self, operation):
return
def __neg__(self):
self.generic_operation('*',-1.0)
return self.res
def rescale(self, new_length):
self.res = multi_vector()
for i in range(len(self.vectors)):
self.res.vectors.append(self.vectors[i].rescale(new_length))
self.res.keys.append(self.keys[i])
return self.res
def rotate_multi_vector_around_an_axis(theta, axis, v):
""" both axis ans v must be multi_vectors """
if axis.keys != v.keys:
raise 'Incompatible keys in rotate multi_vector'
res = multi_vector()
for i in range(len(v.keys)):
res.vectors.append(rotate_vector_around_an_axis(theta, axis.vectors[i], v.vectors[i]))
res.keys.append(v.keys[i])
return res

212
Source/version.py Normal file
View File

@@ -0,0 +1,212 @@
import math
import Source.lib as lib
import sys, os
import Source.calculations as calculations
import Source.parameters
class version:
def __init__(self,parameters):
self.parameters = parameters
return
# desolvation
def calculate_desolvation(self, group):
return self.desolvation_model(self.parameters, group)
def calculatePairWeight(self, Nmass1, Nmass2):
return self.weight_pair_method(self.parameters, Nmass1, Nmass2)
# side chains
def hydrogen_bond_interaction(self, group1, group2):
return self.hydrogen_bond_interaction_model(group1, group2, self)
def calculateSideChainEnergy(self, distance, dpka_max, cutoff, weight, f_angle):
return self.sidechain_interaction_model(distance, dpka_max, cutoff, f_angle) # weight is ignored in 3.0 Sep07
# coulomb
def electrostatic_interaction(self, group1, group2, distance):
return self.electrostatic_interaction_model(group1, group2, distance, self)
def calculateCoulombEnergy(self, distance, weight):
return self.coulomb_interaction_model(distance, weight, self.parameters)
def checkCoulombPair(self, group1, group2, distance):
return self.check_coulomb_pair_method(self.parameters, group1, group2, distance)
# backbone re-organisation
def calculateBackBoneReorganization(self, conformation):
return self.backbone_reorganisation_method(self.parameters, conformation)
# exceptions
def checkExceptions(self, group1, group2):
return self.exception_check_method(self, group1, group2)
def setup_bonding_and_protonation(self, molecular_container):
return self.molecular_preparation_method(self.parameters, molecular_container)
def setup_bonding(self, molecular_container):
return self.prepare_bonds(self.parameters, molecular_container)
class version_A(version):
def __init__(self, parameters):
# set the calculation rutines used in this version
version.__init__(self, parameters)
# atom naming, bonding, and protonation
self.molecular_preparation_method = Source.calculations.setup_bonding_and_protonation
self.prepare_bonds = Source.calculations.setup_bonding
# desolvation related methods
self.desolvation_model = calculations.radial_volume_desolvation
self.weight_pair_method = calculations.calculatePairWeight
# side chain methods
self.sidechain_interaction_model = Source.calculations.HydrogenBondEnergy
self.hydrogen_bond_interaction_model = Source.calculations.hydrogen_bond_interaction
# colomb methods
self.electrostatic_interaction_model = Source.calculations.electrostatic_interaction
self.check_coulomb_pair_method = Source.calculations.checkCoulombPair
self.coulomb_interaction_model = Source.calculations.CoulombEnergy
#backbone
self.backbone_interaction_model = Source.calculations.HydrogenBondEnergy
self.backbone_reorganisation_method = Source.calculations.BackBoneReorganization
# exception methods
self.exception_check_method = Source.calculations.checkExceptions
return
def get_hydrogen_bond_parameters(self, atom1, atom2):
dpka_max = self.parameters.sidechain_interaction
cutoff = self.parameters.sidechain_cutoffs.get_value(atom1.group_type, atom2.group_type)
return [dpka_max, cutoff]
def get_backbone_hydrogen_bond_parameters(self, backbone_atom, atom):
if backbone_atom.group_type == 'BBC':
if atom.group_type in self.parameters.backbone_CO_hydrogen_bond.keys():
[v,c1,c2] = self.parameters.backbone_CO_hydrogen_bond[atom.group_type]
return [v,[c1,c2]]
if backbone_atom.group_type == 'BBN':
if atom.group_type in self.parameters.backbone_NH_hydrogen_bond.keys():
[v,c1,c2] = self.parameters.backbone_NH_hydrogen_bond[atom.group_type]
return [v,[c1,c2]]
return None
class simple_hb(version_A):
def __init__(self, parameters):
# set the calculation rutines used in this version
version_A.__init__(self, parameters)
print('Using simple hb model')
return
def get_hydrogen_bond_parameters(self, atom1, atom2):
return self.parameters.hydrogen_bonds.get_value(atom1.element, atom2.element)
def get_backbone_hydrogen_bond_parameters(self, backbone_atom, atom):
return self.parameters.hydrogen_bonds.get_value(backbone_atom.element, atom.element)
class element_based_ligand_interactions(version_A):
def __init__(self, parameters):
# set the calculation rutines used in this version
version_A.__init__(self, parameters)
print('Using detailed SC model!')
return
def get_hydrogen_bond_parameters(self, atom1, atom2):
if not 'hetatm' in [atom1.type, atom2.type]:
# this is a protein-protein interaction
dpka_max = self.parameters.sidechain_interaction.get_value(atom1.group_type, atom2.group_type)
cutoff = self.parameters.sidechain_cutoffs.get_value(atom1.group_type, atom2.group_type)
return [dpka_max, cutoff]
# at least one ligand atom is involved in this interaction
# make sure that we are using the heavy atoms for finding paramters
elements = []
for a in [atom1, atom2]:
if a.element == 'H': elements.append(a.bonded_atoms[0].element)
else: elements.append(a.element)
return self.parameters.hydrogen_bonds.get_value(elements[0], elements[1])
def get_backbone_hydrogen_bond_parameters(self, backbone_atom, atom):
if atom.type == 'atom':
# this is a backbone-protein interaction
if backbone_atom.group_type == 'BBC' and\
atom.group_type in self.parameters.backbone_CO_hydrogen_bond.keys():
[v,c1,c2] = self.parameters.backbone_CO_hydrogen_bond[atom.group_type]
return [v,[c1,c2]]
if backbone_atom.group_type == 'BBN' and\
atom.group_type in self.parameters.backbone_NH_hydrogen_bond.keys():
[v,c1,c2] = self.parameters.backbone_NH_hydrogen_bond[atom.group_type]
return [v,[c1,c2]]
else:
# this is a backbone-ligand interaction
# make sure that we are using the heavy atoms for finding paramters
elements = []
for a in [backbone_atom, atom]:
if a.element == 'H': elements.append(a.bonded_atoms[0].element)
else: elements.append(a.element)
res = self.parameters.hydrogen_bonds.get_value(elements[0], elements[1])
if not res:
print('Could not determine backbone interaction parameters for:',
backbone_atom,atom)
return
return None
class propka30(version):
def __init__(self, parameters):
# set the calculation rutines used in this version
version.__init__(self, parameters)
# atom naming, bonding, and protonation
self.molecular_preparation_method = Source.calculations.setup_bonding_and_protonation_30_style
# desolvation related methods
self.desolvation_model = calculations.radial_volume_desolvation
self.weight_pair_method = calculations.calculatePairWeight
# side chain methods
self.sidechain_interaction_model = Source.calculations.HydrogenBondEnergy
# colomb methods
self.check_coulomb_pair_method = Source.calculations.checkCoulombPair
self.coulomb_interaction_model = Source.calculations.CoulombEnergy
#backbone
self.backbone_reorganisation_method = Source.calculations.BackBoneReorganization
# exception methods
self.exception_check_method = Source.calculations.checkExceptions
return
def get_hydrogen_bond_parameters(self, atom1, atom2):
dpka_max = self.parameters.sidechain_interaction.get_value(atom1.group_type, atom2.group_type)
cutoff = self.parameters.sidechain_cutoffs.get_value(atom1.group_type, atom2.group_type)
return [dpka_max, cutoff]

398
propka.cfg Normal file
View File

@@ -0,0 +1,398 @@
# PropKa configuration file
version version_A
# Model pKa values
model_pkas C- 3.20
model_pkas ASP 3.80
model_pkas GLU 4.50
model_pkas HIS 6.50
model_pkas CYS 9.00
model_pkas TYR 10.00
model_pkas LYS 10.50
model_pkas ARG 12.50
#model_pkas SER 14.20 Jack Kyte: Structure in Protein Chemistry, 1995, Garland Publishing, Inc New York and London
model_pkas N+ 8.00
model_pkas CG 11.50
model_pkas C2N 11.50
model_pkas N30 10.00
model_pkas N31 10.00
model_pkas N32 10.00
model_pkas N33 10.00
model_pkas NAR 5.00
model_pkas OCO 4.50
model_pkas SH 10.00
model_pkas OP 6.00
# Custom ligand pKa values
# P. Acharya, P. Cheruku, S. Chatterjee, S. Acharya, and, J. Chattopadhyaya:
# Measurement of Nucleobase pKa Values in Model Mononucleotides
# Shows RNA-RNA Duplexes To Be More Stable than DNA-DNA Duplexes
# Journal of the American Chemical Society 2004 126 (9), 2862-2869
#
custom_model_pkas DA-N1 3.82
custom_model_pkas DA-N3 3.82
custom_model_pkas DA-N7 3.82
custom_model_pkas DA-OP1 1.00
custom_model_pkas DA-OP2 1.00
custom_model_pkas DG-N1 9.59
custom_model_pkas DG-N3 9.59
custom_model_pkas DG-N7 9.59
custom_model_pkas DG-OP1 1.00
custom_model_pkas DG-OP2 1.00
custom_model_pkas DC-N3 4.34
custom_model_pkas DC-OP1 1.00
custom_model_pkas DC-OP2 1.00
custom_model_pkas DT-N3 10.12
custom_model_pkas DT-OP1 1.00
custom_model_pkas DT-OP2 1.00
# protein group mapping
protein_group_mapping ASP-CG COO
protein_group_mapping GLU-CD COO
protein_group_mapping HIS-CG HIS
protein_group_mapping CYS-SG CYS
protein_group_mapping TYR-OH TYR
protein_group_mapping LYS-NZ LYS
protein_group_mapping ARG-CZ ARG
#protein_group_mapping SER-OG SER
protein_group_mapping THR-OG1 ROH
protein_group_mapping SER-OG ROH#
protein_group_mapping ASN-CG AMD
protein_group_mapping GLN-CD AMD
protein_group_mapping TRP-NE1 TRP
# matrix for propka interactions
# 'N' non-iterative interaction
# 'I' iterative interaction
# '-' no interaction
#CYS
interaction_matrix CYS I#N+
interaction_matrix N+ N I#HIS
interaction_matrix HIS I N I#LYS
interaction_matrix LYS N N N I#AMD
interaction_matrix AMD N - N - -#COO
interaction_matrix COO I N I N N I#ARG
interaction_matrix ARG N N N N - N I#TRP
interaction_matrix TRP N - - - - N - -#ROH
interaction_matrix ROH N - - - - N - - -#TYR
interaction_matrix TYR N I I I N N N N N I#SER
interaction_matrix SER N N N N N N I N N N I #CG
interaction_matrix CG N N N N - N I - - N I I#C2N
interaction_matrix C2N N N N N - N I - - N I I I#N30
interaction_matrix N30 N I N N - N N - - I N I I I#N31
interaction_matrix N31 N I N N - N N - - I N I I I I#N32
interaction_matrix N32 N I N N - N N - - I N I I I I I#N33
interaction_matrix N33 N I N N - N N - - I N I I I I I I#NAR
interaction_matrix NAR I N I I N I N - - I N N N N N N N I#OCO
interaction_matrix OCO I N I N N I N N N N N N N N N N N I I#NP1
interaction_matrix NP1 N - N - - N - - - N N - - - - - - N N -#OH
interaction_matrix OH N - - - - N - - - N N - - - - - - - N - -#O3
interaction_matrix O3 N - N - - N - - - N N - - - - - - N N - - -#CL
interaction_matrix CL N - N - - N - - - N N - - - - - - N N - - - -#F
interaction_matrix F N - N - - N - - - N N - - - - - - N N - - - - -#NAM
interaction_matrix NAM N - N - - N - - - N N - - - - - - N N - - - - - -#N1
interaction_matrix N1 N - N - - N - - - N N - - - - - - N N - - - - - - -#O2
interaction_matrix O2 N - N - - N - - - N N - - - - - - N N - - - - - - - -#OP
interaction_matrix OP I N I N N I N N N N N N N N N N N I I N N N N N N N N I#SH
interaction_matrix SH I N N N N N N N N N N I I I I I I N N N N N N N N N N N I
# Cutoff values for side chain interactions
# default value
sidechain_cutoffs default 3.0 4.0
# COO
sidechain_cutoffs COO COO 2.5 3.5
Sidechain_cutoffs COO SER 2.65 3.65
sidechain_cutoffs COO ARG 1.85 2.85
sidechain_cutoffs COO LYS 2.85 3.85
sidechain_cutoffs COO HIS 2.0 3.0
sidechain_cutoffs COO AMD 2.0 3.0
sidechain_cutoffs COO TRP 2.0 3.0
sidechain_cutoffs COO ROH 2.65 3.65
sidechain_cutoffs COO TYR 2.65 3.65
sidechain_cutoffs COO N+ 2.85 3.85
sidechain_cutoffs COO CG 1.85 2.85
sidechain_cutoffs COO C2N 1.85 2.85
sidechain_cutoffs COO N30 2.85 3.85
sidechain_cutoffs COO N31 2.85 3.85
sidechain_cutoffs COO N32 2.85 3.85
sidechain_cutoffs COO N33 2.85 3.85
sidechain_cutoffs COO NAR 2.0 3.0
sidechain_cutoffs COO OCO 2.5 3.5
sidechain_cutoffs COO OH 2.65 3.65
sidechain_cutoffs COO NAM 2.0 3.0
# SER
sidechain_cutoffs SER SER 3.5 4.5
sidechain_cutoffs SER ARG 2.5 4.0
sidechain_cutoffs SER HIS 2.0 3.0
sidechain_cutoffs SER AMD 2.5 3.5
sidechain_cutoffs SER CYS 3.5 4.5
sidechain_cutoffs SER TRP 2.5 3.5
sidechain_cutoffs SER ROH 3.5 4.5
sidechain_cutoffs SER CG 2.5 4.0
sidechain_cutoffs SER C2N 2.5 4.0
sidechain_cutoffs SER NAR 2.0 3.0
sidechain_cutoffs SER OH 3.5 4.5
sidechain_cutoffs SER SH 3.5 4.5
sidechain_cutoffs SER TYR 3.5 4.5
sidechain_cutoffs SER N+ 3.0 4.5
sidechain_cutoffs SER NAM 2.5 3.5
# ARG
sidechain_cutoffs ARG CYS 2.5 4.0
sidechain_cutoffs ARG TYR 2.5 4.0
sidechain_cutoffs ARG OCO 1.85 2.85
sidechain_cutoffs ARG SH 2.5 4.0
# HIS
sidechain_cutoffs HIS AMD 2.0 3.0
sidechain_cutoffs HIS TYR 2.0 3.0
sidechain_cutoffs HIS OCO 2.0 3.0
# CYS
sidechain_cutoffs CYS CYS 3.0 5.0
sidechain_cutoffs CYS TRP 2.5 3.5
sidechain_cutoffs CYS ROH 3.5 4.5
sidechain_cutoffs CYS AMD 2.5 3.5
sidechain_cutoffs CYS TYR 3.5 4.5
sidechain_cutoffs CYS N+ 3.0 4.5
sidechain_cutoffs CYS CG 2.5 4.0
sidechain_cutoffs CYS C2N 2.5 4.0
sidechain_cutoffs CYS N30 3.0 4.5
sidechain_cutoffs CYS N31 3.0 4.5
sidechain_cutoffs CYS N32 3.0 4.5
sidechain_cutoffs CYS N33 3.0 4.5
sidechain_cutoffs CYS OH 3.5 4.5
sidechain_cutoffs CYS NAM 2.5 3.5
sidechain_cutoffs CYS SH 3.0 5.0
# TYR
sidechain_cutoffs TYR TYR 3.5 4.5
sidechain_cutoffs TYR N+ 3.0 4.5
sidechain_cutoffs TYR AMD 2.5 3.5
sidechain_cutoffs TYR TRP 2.5 3.5
sidechain_cutoffs TYR ROH 3.5 4.5
sidechain_cutoffs TYR CG 2.5 4.0
sidechain_cutoffs TYR C2N 2.5 4.0
sidechain_cutoffs TYR OCO 2.65 3.65
sidechain_cutoffs TYR NAR 2.0 3.0
sidechain_cutoffs TYR OH 3.5 4.5
sidechain_cutoffs TYR NAM 2.5 3.5
sidechain_cutoffs TYR SH 3.5 4.5
# N+
sidechain_cutoffs N+ OCO 2.85 3.85
sidechain_cutoffs N+ SH 3.0 4.5
# LYS
sidechain_cutoffs LYS OCO 2.85 3.85
# OCO
sidechain_cutoffs OCO OCO 2.5 3.5
sidechain_cutoffs OCO TRP 2.0 3.0
sidechain_cutoffs OCO ROH 2.65 3.65
sidechain_cutoffs OCO AMD 2.0 3.0
sidechain_cutoffs OCO CG 1.85 2.85
sidechain_cutoffs OCO C2N 1.85 2.85
sidechain_cutoffs OCO N30 2.85 3.85
sidechain_cutoffs OCO N31 2.85 3.85
sidechain_cutoffs OCO N32 2.85 3.85
sidechain_cutoffs OCO N33 2.85 3.85
sidechain_cutoffs OCO NAR 2.0 3.0
sidechain_cutoffs OCO OH 2.65 3.65
sidechain_cutoffs OCO NAM 2.0 3.0
# NAR
sidechain_cutoffs NAR AMD 2.0 3.0
# SH
sidechain_cutoffs SH ROH 3.5 4.5
sidechain_cutoffs SH TRP 2.5 3.5
sidechain_cutoffs SH AMD 2.5 3.5
sidechain_cutoffs SH NAM 2.5 3.5
sidechain_cutoffs SH CG 2.5 4.0
sidechain_cutoffs SH C2N 2.5 4.0
sidechain_cutoffs SH OH 3.5 4.5
sidechain_cutoffs SH SH 3.0 5.0
# Maximal interaction energies for side chains
sidechain_interaction 0.85
# Angular dependent sidechain interactions
angular_dependent_sidechain_interactions HIS
angular_dependent_sidechain_interactions ARG
angular_dependent_sidechain_interactions AMD
angular_dependent_sidechain_interactions TRP
# exception interaction values
COO_HIS_exception 1.60
OCO_HIS_exception 1.60
CYS_HIS_exception 1.60
CYS_CYS_exception 3.60
# Coulomb interaction parameters
coulomb_cutoff1 4.0
coulomb_cutoff2 10.0
coulomb_diel 80.0
# Backbone hydrogen bond parameters
backbone_NH_hydrogen_bond COO -0.85 2.00 3.00
#backbone_NH_hydrogen_bond C- -0.85 2.00 3.00
backbone_NH_hydrogen_bond CYS -0.85 3.00 4.00
backbone_NH_hydrogen_bond TYR -0.85 2.20 3.20
backbone_NH_hydrogen_bond OCO -0.85 2.00 3.50
backbone_NH_hydrogen_bond NAR -0.85 2.00 3.50
backbone_CO_hydrogen_bond HIS 0.85 2.00 3.00
backbone_CO_hydrogen_bond OCO 0.85 3.00 4.00
backbone_CO_hydrogen_bond CG 0.85 2.00 4.00
backbone_CO_hydrogen_bond C2N 0.85 2.00 4.00
backbone_CO_hydrogen_bond N30 0.85 2.00 4.00
backbone_CO_hydrogen_bond N31 0.85 2.00 4.00
backbone_CO_hydrogen_bond N32 0.85 2.00 4.00
backbone_CO_hydrogen_bond N33 0.85 2.00 4.00
backbone_CO_hydrogen_bond NAR 0.85 2.00 3.50
# Group charges
charge COO -1
charge HIS +1
charge CYS -1
charge TYR -1
charge LYS +1
charge ARG +1
charge N+ +1
charge C- -1
charge OCO -1
charge SER -1
charge CG +1
charge C2N +1
charge N30 +1
charge N31 +1
charge N32 +1
charge N33 +1
charge NAR +1
charge SH -1
charge OP -1
# list of acids
acid_list ASP
acid_list GLU
acid_list CYS
acid_list TYR
acid_list SER
acid_list C-
acid_list OCO
acid_list OP
acid_list SH
# list of bases
base_list ARG
base_list LYS
base_list HIS
base_list N+
base_list CG
base_list C2N
base_list N30
base_list N31
base_list N32
base_list N33
base_list NAR
# list of groups used in backbone reorganisation calculations
backbone_reorganisation_list ASP
backbone_reorganisation_list GLU
# Residues that should be ignored
ignore_residues HOH
ignore_residues H2O
ignore_residues HOH
ignore_residues SO4
ignore_residues PO4
ignore_residues PEG
ignore_residues EPE
#ignore_residues NAG
ignore_residues TRS
# Relative Van der Waals volume parameters for the radial volume model
# Radii adopted from Bondi, A. (1964). "Van der Waals Volumes and Radii". J. Phys. Chem. 68 (3): 441-51
VanDerWaalsVolume C 1.40 # radius: 1.70, volume: 20.58 all 'C' and 'CA' atoms
VanDerWaalsVolume C4 2.64 # 38.79 hydrodphobic carbon atoms + unidentified atoms
VanDerWaalsVolume N 1.06 # radius: 1.55, volume: 15.60 all nitrogen atoms
VanDerWaalsVolume O 1.00 # radius: 1.52, volume: 14.71 all oxygen atoms
VanDerWaalsVolume S 1.66 # radius: 1.80, volume: 24.43 all sulphur atoms
VanDerWaalsVolume F 0.90 # raidus: 1.47, volume: 13.30 for fluorine
VanDerWaalsVolume Cl 1.53 # radius: 1.75, volume: 22.44 for chlorine
VanDerWaalsVolume P 1.66 # radius: 1.80, volume: 24.42 for phosphorus
# Other desolvation parameters
desolvationSurfaceScalingFactor 0.25
desolvationPrefactor -13.0
desolvationAllowance 0.0
desolv_cutoff 20.0
buried_cutoff 15.0
Nmin 280
Nmax 560
# Ligand groups
ligand_typing groups
min_bond_distance_for_hydrogen_bonds 4
# covalent coupling
coupling_max_number_of_bonds 3
shared_determinants 0
common_charge_centre 0
remove_penalised_group 1
# non-covalent coupling
max_intrinsic_pKa_diff 2.0
min_interaction_energy 0.5
max_free_energy_diff 1.0
min_swap_pka_shift 1.0
min_pka 0.0
max_pka 10.0
pH variable
reference neutral
# ions
ions 1P 1 # generic charged atoms
ions 2P 2
ions 1N -1
ions 2N -2
ions MG 2 #Magnesium Ion
ions CA 2 #Calcium Ion
ions ZN 2 #Zinc Ion
ions NA 1 #Sodium Ion
ions CL -1 #Chloride Ion
ions MN 2 #Manganese (ii) Ion
ions K 1 #Potassium Ion
ions CD 2 #Cadmium Ion
ions FE 3 #Fe (iii) Ion
ions SR 2 #Strontium Ion
ions CU 2 #Copper (ii) Ion
ions IOD -1 #Iodide Ion
ions HG 2 #Mercury (ii) Ion
ions BR -1 #Bromide Ion
ions CO 2 #Cobalt (ii) Ion
ions NI 2 #Nickel (ii) Ion
ions FE2 2 #Fe (ii) Ion
# write out order of residues
write_out_order ASP
write_out_order GLU
write_out_order C-
write_out_order HIS
write_out_order CYS
write_out_order TYR
write_out_order LYS
write_out_order ARG
write_out_order SER
write_out_order N+
write_out_order CG
write_out_order C2N
write_out_order N30
write_out_order N31
write_out_order N32
write_out_order N33
write_out_order NAR
write_out_order OCO
write_out_order SH
write_out_order OP

23
propka.py Executable file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python3.1
import string,re,sys,os,math
import Source.lib, Source.molecular_container
def main():
"""
Reads in structure files, calculates pKa values, and prints pKa files
"""
# loading options, flaggs and arguments
options, pdbfiles = Source.lib.loadOptions()
for pdbfile in pdbfiles:
my_molecule = Source.molecular_container.Molecular_container(pdbfile, options)
my_molecule.calculate_pka()
my_molecule.write_pka()
if __name__ == '__main__':
#import cProfile
#cProfile.run('main()',sort=1)
main()