anzhi2710gmailcom's picture
Upload folder using huggingface_hub
10f2621 verified
Raw
History Blame Contribute Delete
8.01 kB
from Bio.PDB import *
import numpy as np
from sklearn.neighbors import KDTree
"""
computeCharges.py: Wrapper function to compute hydrogen bond potential (free electrons/protons) in the surface
Pablo Gainza - LPDI STI EPFL 2019
This file is part of MaSIF.
Released under an Apache License 2.0
"""
from default_config.chemistry import (
polarHydrogens,
radii,
acceptorAngleAtom,
acceptorPlaneAtom,
hbond_std_dev,
donorAtom,
)
# Compute vertex charges based on hydrogen bond potential.
# pdb_filename: The filename of the protonated protein.
# vertices: The surface vertices of the protonated protein
# The name of each vertex in the format, example: B_125_x_ASN_ND2_Green
# where B is chain, 125 res id, x the insertion, ASN aatype, ND2 the name of the
# atom, and green is not used anymore.
def computeCharges(pdb_filename, vertices, names):
parser = PDBParser(QUIET=True)
struct = parser.get_structure(pdb_filename, pdb_filename + ".pdb")
residues = {}
for res in struct.get_residues():
chain_id = res.get_parent().get_id()
if chain_id == "":
chain_id = " "
residues[(chain_id, res.get_id())] = res
atoms = Selection.unfold_entities(struct, "A")
satisfied_CO, satisfied_HN = computeSatisfied_CO_HN(atoms)
charge = np.array([0.0] * len(vertices))
# Go over every vertex
for ix, name in enumerate(names):
fields = name.split("_")
chain_id = fields[0]
if chain_id == "":
chain_id = " "
if fields[2] == "x":
fields[2] = " "
res_id = (" ", int(fields[1]), fields[2])
aa = fields[3]
atom_name = fields[4]
# Ignore atom if it is BB and it is already satisfied.
if atom_name == "H" and res_id in satisfied_HN:
continue
if atom_name == "O" and res_id in satisfied_CO:
continue
# Compute the charge of the vertex
charge[ix] = computeChargeHelper(
atom_name, residues[(chain_id, res_id)], vertices[ix]
)
return charge
# Compute the charge of a vertex in a residue.
def computeChargeHelper(atom_name, res, v):
res_type = res.get_resname()
# Check if it is a polar hydrogen.
if isPolarHydrogen(atom_name, res):
donor_atom_name = donorAtom[atom_name]
a = res[donor_atom_name].get_coord() # N/O
b = res[atom_name].get_coord() # H
# Donor-H is always 180.0 degrees, = pi
angle_deviation = computeAngleDeviation(a, b, v, np.pi)
angle_penalty = computeAnglePenalty(angle_deviation)
return 1.0 * angle_penalty
# Check if it is an acceptor oxygen or nitrogen
elif isAcceptorAtom(atom_name, res):
acceptor_atom = res[atom_name]
b = acceptor_atom.get_coord()
try:
a = res[acceptorAngleAtom[atom_name]].get_coord()
except:
return 0.0
# 120 degress for acceptor
angle_deviation = computeAngleDeviation(a, b, v, 2 * np.pi / 3)
# TODO: This should not be 120 for all atoms, i.e. for HIS it should be
# ~125.0
angle_penalty = computeAnglePenalty(angle_deviation)
plane_penalty = 1.0
if atom_name in acceptorPlaneAtom:
try:
d = res[acceptorPlaneAtom[atom_name]].get_coord()
except:
return 0.0
plane_deviation = computePlaneDeviation(d, a, b, v)
plane_penalty = computeAnglePenalty(plane_deviation)
return -1.0 * angle_penalty * plane_penalty
# Compute the
return 0.0
# Compute the absolute value of the deviation from theta
def computeAngleDeviation(a, b, c, theta):
return abs(calc_angle(Vector(a), Vector(b), Vector(c)) - theta)
# Compute the angle deviation from a plane
def computePlaneDeviation(a, b, c, d):
dih = calc_dihedral(Vector(a), Vector(b), Vector(c), Vector(d))
dev1 = abs(dih)
dev2 = np.pi - abs(dih)
return min(dev1, dev2)
# angle_deviation from ideal value. TODO: do a more data-based solution
def computeAnglePenalty(angle_deviation):
# Standard deviation: hbond_std_dev
return max(0.0, 1.0 - (angle_deviation / (hbond_std_dev)) ** 2)
def isPolarHydrogen(atom_name, res):
if atom_name in polarHydrogens[res.get_resname()]:
return True
else:
return False
def isAcceptorAtom(atom_name, res):
if atom_name.startswith("O"):
return True
else:
if res.get_resname() == "HIS":
if atom_name == "ND1" and "HD1" not in res:
return True
if atom_name == "NE2" and "HE2" not in res:
return True
return False
# Compute the list of backbone C=O:H-N that are satisfied. These will be ignored.
def computeSatisfied_CO_HN(atoms):
ns = NeighborSearch(atoms)
satisfied_CO = set()
satisfied_HN = set()
for atom1 in atoms:
res1 = atom1.get_parent()
if atom1.get_id() == "O":
neigh_atoms = ns.search(atom1.get_coord(), 2.5, level="A")
for atom2 in neigh_atoms:
if atom2.get_id() == "H":
res2 = atom2.get_parent()
# Ensure they belong to different residues.
if res2.get_id() != res1.get_id():
# Compute the angle N-H:O, ideal value is 180 (but in
# helices it is typically 160) 180 +-30 = pi
angle_N_H_O_dev = computeAngleDeviation(
res2["N"].get_coord(),
atom2.get_coord(),
atom1.get_coord(),
np.pi,
)
# Compute angle H:O=C, ideal value is ~160 +- 20 = 8*pi/9
angle_H_O_C_dev = computeAngleDeviation(
atom2.get_coord(),
atom1.get_coord(),
res1["C"].get_coord(),
8 * np.pi / 9,
)
## Allowed deviations: 30 degrees (pi/6) and 20 degrees
# (pi/9)
if (
angle_N_H_O_dev - np.pi / 6 < 0
and angle_H_O_C_dev - np.pi / 9 < 0.0
):
satisfied_CO.add(res1.get_id())
satisfied_HN.add(res2.get_id())
return satisfied_CO, satisfied_HN
# Compute the charge of a new mesh, based on the charge of an old mesh.
# Use the top vertex in distance, for now (later this should be smoothed over 3
# or 4 vertices)
def assignChargesToNewMesh(new_vertices, old_vertices, old_charges, seeder_opts):
dataset = old_vertices
testset = new_vertices
new_charges = np.zeros(len(new_vertices))
if seeder_opts["feature_interpolation"]:
num_inter = 4 # Number of interpolation features
# Assign k old vertices to each new vertex.
kdt = KDTree(dataset)
dists, result = kdt.query(testset, k=num_inter)
# Square the distances (as in the original pyflann)
dists = np.square(dists)
# The size of result is the same as new_vertices
for vi_new in range(len(result)):
vi_old = result[vi_new]
dist_old = dists[vi_new]
# If one vertex is right on top, ignore the rest.
if dist_old[0] == 0.0:
new_charges[vi_new] = old_charges[vi_old[0]]
continue
total_dist = np.sum(1 / dist_old)
for i in range(num_inter):
new_charges[vi_new] += (
old_charges[vi_old[i]] * (1 / dist_old[i]) / total_dist
)
else:
# Assign k old vertices to each new vertex.
kdt = KDTree(dataset)
dists, result = kdt.query(testset)
new_charges = old_charges[result]
return new_charges