Partial de-lint of calculations.py.

This really should be 3 separate files.  The initial de-lint focused on
the distance and hydrogen addition functions.
This commit is contained in:
Nathan Baker
2020-05-23 12:34:31 -07:00
parent f2aef4ce4f
commit f444d138ee

View File

@@ -7,6 +7,72 @@ import propka.bonds
from propka.lib import info, warning from propka.lib import info, warning
# TODO - this file should be broken into three separate files:
# * calculations.py - includes basic functions for calculating distances, etc.
# * hydrogen.py - includes bonding and protonation functions
# * energy.py - includes energy functions (dependent on distance functions)
# TODO - the next set of functions form a distinct "module" for distance calculation
# Maximum distance used to bound calculations of smallest distance
MAX_DISTANCE = 1e6
def squared_distance(atom1, atom2):
"""Calculate the squared distance between two atoms.
Args:
atom1: first atom for distance calculation
atom2: second atom for distance calculation
Returns:
distance squared
"""
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):
"""Calculate the distance between two atoms.
Args:
atom1: first atom for distance calculation
atom2: second atom for distance calculation
Returns:
distance
"""
return math.sqrt(squared_distance(atom1,atom2))
def get_smallest_distance(atoms1, atoms2):
"""Calculate the smallest distance between two groups of atoms.
Args:
atoms1: atom group 1
atoms2: atom group 2
Returns:
smallest distance between groups
"""
res_distance = MAX_DISTANCE
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]
# TODO - the next set of functions form a distinct "module" for hydrogen addition
def setup_bonding_and_protonation(parameters, molecular_container): def setup_bonding_and_protonation(parameters, molecular_container):
"""Set up bonding and protonation for a molecule. """Set up bonding and protonation for a molecule.
@@ -48,256 +114,245 @@ def set_ligand_atom_names(molecular_container):
molecular_container.conformations[name].set_ligand_atom_names() molecular_container.conformations[name].set_ligand_atom_names()
def addArgHydrogen(residue): def add_arg_hydrogen(residue):
""" """Adds Arg hydrogen atoms to residues according to the 'old way'.
Adds Arg hydrogen atoms to residues according to the 'old way'.
Args:
residue: arginine residue to protonate
Returns:
list of hydrogen atoms
""" """
#info('Adding arg H',residue) #info('Adding arg H',residue)
for atom in residue: for atom in residue:
if atom.name == "CD": if atom.name == "CD":
CD = atom cd_atom = atom
elif atom.name == "CZ": elif atom.name == "CZ":
CZ = atom cz_atom = atom
elif atom.name == "NE": elif atom.name == "NE":
NE = atom ne_atom = atom
elif atom.name == "NH1": elif atom.name == "NH1":
NH1 = atom nh1_atom = atom
elif atom.name == "NH2": elif atom.name == "NH2":
NH2 = atom nh2_atom = atom
h1_atom = protonate_sp2(cd_atom, ne_atom, cz_atom)
h1_atom.name = "HE"
h2_atom = protonate_direction(nh1_atom, ne_atom, cz_atom)
h2_atom.name = "HN1"
h3_atom = protonate_direction(nh1_atom, ne_atom, cd_atom)
h3_atom.name = "HN2"
h4_atom = protonate_direction(nh2_atom, ne_atom, cz_atom)
h4_atom.name = "HN3"
h5_atom = protonate_direction(nh2_atom, ne_atom, h1_atom)
h5_atom.name = "HN4"
return [h1_atom, h2_atom, h3_atom, h4_atom, h5_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 add_his_hydrogen(residue):
"""Adds His hydrogen atoms to residues according to the 'old way'.
def addHisHydrogen(residue): Args:
""" residue: histidine residue to protonate
Adds His hydrogen atoms to residues according to the 'old way'.
""" """
for atom in residue: for atom in residue:
if atom.name == "CG": if atom.name == "CG":
CG = atom cg_atom = atom
elif atom.name == "ND1": elif atom.name == "ND1":
ND = atom nd_atom = atom
elif atom.name == "CD2": elif atom.name == "CD2":
CD = atom cd_atom = atom
elif atom.name == "CE1": elif atom.name == "CE1":
CE = atom ce_atom = atom
elif atom.name == "NE2": elif atom.name == "NE2":
NE = atom ne_atom = atom
HD = protonateSP2([CG, ND, CE]) hd_atom = protonate_sp2(cg_atom, nd_atom, ce_atom)
HD.name = "HND" hd_atom.name = "HND"
HE = protonateSP2([CD, NE, CE]) he_atom = protonate_sp2(cd_atom, ne_atom, ce_atom)
HE.name = "HNE" he_atom.name = "HNE"
return
def addTrpHydrogen(residue):
def add_trp_hydrogen(residue):
"""Adds Trp hydrogen atoms to residues according to the 'old way'.
Args:
residue: tryptophan residue to protonate
""" """
Adds Trp hydrogen atoms to residues according to the 'old way'. cd_atom = None
""" ne_atom = None
CD = None
NE = None
DE = None
for atom in residue: for atom in residue:
if atom.name == "CD1": if atom.name == "CD1":
CD = atom cd_atom = atom
elif atom.name == "NE1": elif atom.name == "NE1":
NE = atom ne_atom = atom
elif atom.name == "CE2": elif atom.name == "CE2":
CE = atom ce_atom = atom
if CD == None or NE == None or CE == None: if (cd_atom is None) or (ne_atom is None) or (ce_atom is None):
str = "Did not find all atoms in %s%4d - in %s" % (self.res_name, self.res_num, "addTrpHydrogen()") errstr = "Unable to find all atoms for %s %s" % (residue[0].res_name,
info(str) residue[0].res_num)
sys.exit(0) raise ValueError(errstr)
he_atom = protonate_sp2(cd_atom, ne_atom, ce_atom)
he_atom.name = "HNE"
HE = protonateSP2([CD, NE, CE])
HE.name = "HNE"
return def add_amd_hydrogen(residue):
"""Adds Gln & Asn hydrogen atoms to residues according to the 'old way'.
def addAmdHydrogen(residue): Args:
residue: glutamine or asparagine residue to protonate
""" """
Adds Gln & Asn hydrogen atoms to residues according to the 'old way'. c_atom = None
""" o_atom = None
C = None n_atom = None
O = None
N = None
for atom in residue: for atom in residue:
if (atom.res_name == "GLN" and atom.name == "CD") or (atom.res_name == "ASN" and atom.name == "CG"): if (atom.res_name == "GLN" and atom.name == "CD") or (atom.res_name == "ASN" and atom.name == "CG"):
C = atom c_atom = atom
elif (atom.res_name == "GLN" and atom.name == "OE1") or (atom.res_name == "ASN" and atom.name == "OD1"): elif (atom.res_name == "GLN" and atom.name == "OE1") or (atom.res_name == "ASN" and atom.name == "OD1"):
O = atom o_atom = atom
elif (atom.res_name == "GLN" and atom.name == "NE2") or (atom.res_name == "ASN" and atom.name == "ND2"): elif (atom.res_name == "GLN" and atom.name == "NE2") or (atom.res_name == "ASN" and atom.name == "ND2"):
N = atom n_atom = atom
if (c_atom is None) or (o_atom is None) or (n_atom is None):
errstr = "Unable to find all atoms for %s %s" % (residue[0].res_name,
residue[0].res_num)
raise ValueError(errstr)
h1_atom = protonate_direction(n_atom, o_atom, c_atom)
h1_atom.name = "HN1"
h2_atom = protonate_average_direction(n_atom, c_atom, o_atom)
h2_atom.name = "HN2"
if C == None or O == None or N == None:
str = "Did not find N, C and/or O in %s%4d - in %s" % (atom.res_name, atom.res_num, "addAmdHydrogen()")
info(str)
sys.exit(0)
H1 = protonateDirection([N, O, C]) def add_backbone_hydrogen(residue, o_atom, c_atom):
H1.name = "HN1" """Adds hydrogen backbone atoms to residues according to the old way.
H2 = protonateAverageDirection([N, C, O])
H2.name = "HN2"
return 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.
def addBackBoneHydrogen(residue, O, C): Args:
residue: residue to protonate
o_atom: backbone oxygen atom
c_atom: backbone carbon atom
Returns:
[new backbone oxygen atom, new backbone carbon atom]
""" """
Adds hydrogen backbone atoms to residues according to the old way; dR is wrong for the N-terminus new_c_atom = None
(i.e. first residue) but it doesn't affect anything at the moment. Could be improved, but works new_o_atom = None
for now. n_atom = None
"""
new_C = None
new_O = None
N = None
for atom in residue: for atom in residue:
if atom.name == "N": if atom.name == "N":
N = atom n_atom = atom
if atom.name == "C": if atom.name == "C":
new_C = atom new_c_atom = atom
if atom.name == "O": if atom.name == "O":
new_O = atom new_o_atom = atom
if None in [c_atom, o_atom, n_atom]:
return [new_o_atom, new_c_atom]
if n_atom.res_name == "PRO":
if None in [C,O,N]:
return [new_O,new_C]
if N.res_name == "PRO":
"""PRO doesn't have an H-atom; do nothing""" """PRO doesn't have an H-atom; do nothing"""
else: else:
H = protonateDirection([N, O, C]) h_atom = protonate_direction(n_atom, o_atom, c_atom)
H.name = "H" h_atom.name = "H"
return [new_o_atom,new_c_atom]
return [new_O,new_C]
def protonate_direction(x1_atom, x2_atom, x3_atom):
"""Protonates an atom, x1_atom, given a direction.
New direction for x1_atom proton is (x2_atom -> x3_atom).
Args:
def protonateDirection(list): x1_atom: atom to be protonated
x2_atom: atom for direction
x3_atom: other atom for direction
Returns:
new hydrogen atom
""" """
Protonates an atom, X1, given a direction (X2 -> X3) [X1, X2, X3] dX = (x3_atom.x - x2_atom.x)
""" dY = (x3_atom.y - x2_atom.y)
X1 = list[0] dZ = (x3_atom.z - x2_atom.z)
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 ) length = math.sqrt( dX*dX + dY*dY + dZ*dZ )
x = X1.x + dX/length x = x1_atom.x + dX/length
y = X1.y + dY/length y = x1_atom.y + dY/length
z = X1.z + dZ/length z = x1_atom.z + dZ/length
H = make_new_h(x1_atom,x,y,z)
H = make_new_H(X1,x,y,z)
H.name = "H" H.name = "H"
return H return H
def protonateAverageDirection(list): def protonate_average_direction(x1_atom, x2_atom, x3_atom):
""" """Protonates an atom, x1_atom, given a direction.
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 New direction for x1_atom is (x1_atom/x2_atom -> x3_atom).
dY = (X3.y + X1.y)*0.5 - X2.y Note, this one uses the average of x1_atom & x2_atom (N & O) unlike
dZ = (X3.z + X1.z)*0.5 - X2.z the previous N - C = O
Args:
x1_atom: atom to be protonated
x2_atom: atom for direction
x3_atom: other atom for direction
Returns:
new hydrogen atom
"""
dX = (x3_atom.x + x1_atom.x)*0.5 - x2_atom.x
dY = (x3_atom.y + x1_atom.y)*0.5 - x2_atom.y
dZ = (x3_atom.z + x1_atom.z)*0.5 - x2_atom.z
length = math.sqrt( dX*dX + dY*dY + dZ*dZ ) length = math.sqrt( dX*dX + dY*dY + dZ*dZ )
x = X1.x + dX/length x = x1_atom.x + dX/length
y = X1.y + dY/length y = x1_atom.y + dY/length
z = X1.z + dZ/length z = x1_atom.z + dZ/length
H = make_new_h(x1_atom,x,y,z)
H = make_new_H(X1,x,y,z)
H.name = "H" H.name = "H"
return H return H
def protonateSP2(list): def protonate_sp2(x1_atom, x2_atom, x3_atom):
""" """Protonates a SP2 atom, given a list of atoms
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 Args:
dY = (X1.y + X3.y)*0.5 - X2.y x1_atom: atom to set direction
dZ = (X1.z + X3.z)*0.5 - X2.z x2_atom: atom to be protonated
x3_atom: other atom to set direction
Returns:
new hydrogen atom
"""
dX = (x1_atom.x + x3_atom.x)*0.5 - x2_atom.x
dY = (x1_atom.y + x3_atom.y)*0.5 - x2_atom.y
dZ = (x1_atom.z + x3_atom.z)*0.5 - x2_atom.z
length = math.sqrt( dX*dX + dY*dY + dZ*dZ ) length = math.sqrt( dX*dX + dY*dY + dZ*dZ )
x = X2.x - dX/length x = x2_atom.x - dX/length
y = X2.y - dY/length y = x2_atom.y - dY/length
z = X2.z - dZ/length z = x2_atom.z - dZ/length
H = make_new_h(x2_atom,x,y,z)
H = make_new_H(X2,x,y,z)
H.name = "H" H.name = "H"
return H return H
def make_new_H(atom, x,y,z): def make_new_h(atom, x,y,z):
"""Add a new hydrogen to an atom at the specified position.
Args:
atom: atom to protonate
x: x position of hydrogen
y: y position of hydrogen
z: z position of hydrogen
Returns:
new hydrogen atom
"""
new_H = propka.atom.Atom() new_H = propka.atom.Atom()
new_H.set_property(numb = None, new_H.set_property(numb=None, name='H%s' % atom.name[1:],
name = 'H%s'%atom.name[1:], res_name=atom.res_name, chain_id=atom.chain_id,
res_name = atom.res_name, res_num=atom.res_num, x=x, y=y, z=z, occ=None,
chain_id = atom.chain_id,
res_num = atom.res_num,
x = x,
y = y,
z = z,
occ = None,
beta=None) beta=None)
new_H.element = 'H' new_H.element = 'H'
new_H.bonded_atoms = [atom] new_H.bonded_atoms = [atom]
new_H.charge = 0 new_H.charge = 0
new_H.steric_number = 0 new_H.steric_number = 0
new_H.number_of_lone_pairs = 0 new_H.number_of_lone_pairs = 0
new_H.number_of_protons_to_add = 0 new_H.number_of_protons_to_add = 0
new_H.num_pi_elec_2_3_bonds = 0 new_H.num_pi_elec_2_3_bonds = 0
atom.bonded_atoms.append(new_H) atom.bonded_atoms.append(new_H)
atom.conformation_container.add_atom(new_H) atom.conformation_container.add_atom(new_H)
return new_H return new_H
######## 3.0 style protonation methods end
# TODO - the remaining functions form a dist
# #
# Desolvation methods # Desolvation methods
@@ -416,41 +471,6 @@ def calculate_weight(parameters, Nmass):
return 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): def calculatePairWeight(parameters, Nmass1, Nmass2):
""" """
calculating the atom-pair based desolvation weight calculating the atom-pair based desolvation weight