import numpy as np
import math
try:
from pyrosetta import rosetta
except BaseException:
rosetta = None
try:
import mdtraj as md
except BaseException:
md = None
'''
try:
import Bio as bp
except:
bp = None
'''
def estimate_CB_xyz(C_xyz, N_xyz, CA_xyz):
# ICOOR_INTERNAL CB -122.800000 69.625412 1.521736 CA N C
'''
That is, CB is found 1.52 Ang from CA, at an angle of 70 degrees from the CA-N line, and a dihedral of -123 degrees for CB-CA-N-C.
-ROCCO
'''
# Credit to Rohit Bhattacharya from https://github.com/rbhatta8/protein-design/blob/master/nerf.py
'''
Nerf method of finding 4th coord (d)
in cartesian space
Params:
a, b, c : coords of 3 points
l : bond length between c and d
theta : bond angle between b, c, d (in degrees)
chi : dihedral using a, b, c, d (in degrees)
Returns:
d : tuple of (x, y, z) in cartesian space
'''
length = 1.521736
theta = 69.625412
chi = -122.800000
a = C_xyz
b = N_xyz
c = CA_xyz
# calculate unit vectors AB and BC
ab_unit = (b - a) / np.linalg.norm(b - a)
bc_unit = (c - b) / np.linalg.norm(c - b)
# calculate unit normals n = AB x BC
# and p = n x BC
n_unit = np.cross(ab_unit, bc_unit)
n_unit = n_unit / np.linalg.norm(n_unit)
p_unit = np.cross(n_unit, bc_unit)
# create rotation matrix [BC; p; n] (3x3)
M = np.array([bc_unit, p_unit, n_unit]).T
# convert degrees to radians
theta = np.pi / 180 * theta
chi = np.pi / 180 * chi
# calculate coord pre rotation matrix
d2 = [-length * np.cos(theta), length * np.sin(theta) * np.cos(chi), length * np.sin(theta) * np.sin(chi)]
# calculate with rotation as our final output
return c + np.dot(M, d2)
[docs]class WrappedPose:
"""
This is the base class for all pose representations.
The internal Menten GCN code will use API listed here
"""
def __init__(self, designable_resids=None):
self.CB_estimates = None
self.legal_nbrs = None
self.designable_resids = designable_resids
def get_legal_nbrs(self):
return self.legal_nbrs
def get_atom_xyz(self, resid, atomname):
raise NotImplementedError
def get_phi_psi(self, resid):
# radians
raise NotImplementedError
def get_chi(self, resid, chi_number):
# radians
raise NotImplementedError
def get_name1(self, resid):
raise NotImplementedError
def residues_are_polymer_bonded(self, resid1, resid2):
raise NotImplementedError
def n_residues(self):
raise NotImplementedError
def resid_is_N_term(self, resid):
raise NotImplementedError
def resid_is_C_term(self, resid):
raise NotImplementedError
def resids_are_same_chain(self, resid1, resid2):
raise NotImplementedError
def set_designable_resids(self, resids):
self.designable_resids = resids
def resid_is_designable(self, resid):
assert self.designable_resids is not None
return resid in self.designable_resids
def approximate_ALA_CB(self, resid):
assert hasattr(self, 'CB_estimates')
# if not hasattr( self, 'CB_estimates' ):
if self.CB_estimates is None:
# Lazy initialization
self.CB_estimates = [None for i in range(0, self.n_residues() + 1)]
elif self.CB_estimates[resid] is not None:
return self.CB_estimates[resid]
get_xyz = self.get_atom_xyz
self.CB_estimates[resid] = estimate_CB_xyz(get_xyz(resid, "C"), get_xyz(resid, "N"), get_xyz(resid, "CA"))
return self.CB_estimates[resid]
[docs]class RosettaPoseWrapper(WrappedPose):
"""
This wrapper takes a rosetta pose and requires pyrosetta to be installed
Parameters
---------
pose: Pose
Rosetta pose
"""
def __init__(self, pose):
WrappedPose.__init__(self)
if rosetta is None:
print("RosettaPoseWrapper requires the pyrosetta library to be installed")
raise ImportError
assert isinstance(pose, rosetta.core.pose.Pose)
self.pose = pose
def get_atom_xyz(self, resid, atomname):
xyz = self.pose.residue(resid).xyz(atomname)
return np.asarray([xyz.x, xyz.y, xyz.z])
def get_phi_psi(self, resid):
phipsi = np.asarray([self.pose.phi(resid), self.pose.psi(resid)])
phipsi[0] = math.radians(phipsi[0])
phipsi[1] = math.radians(phipsi[1])
return phipsi
def get_chi(self, resid, chi_number):
if self.pose.residue(resid).nchi() < chi_number:
return 0, False
chi_deg = self.pose.chi(chi_number, resid)
chi_rad = math.radians(chi_deg)
return chi_rad, True
def get_name1(self, resid):
return self.pose.residue(resid).name1()
def residues_are_polymer_bonded(self, resid1, resid2):
return self.pose.residue(resid1).is_polymer_bonded(resid2)
def n_residues(self):
return self.pose.size()
def approximate_ALA_CB_via_mutation(self, resid):
if not self.pose.residue(resid).name1() == 'G':
print("RosettaPoseWrapper.approximate_ALA_CB is only setup for glycine right now")
print(self.pose.residue(resid).name1())
assert False
mutator = rosetta.protocols.simple_moves.MutateResidue(resid, 'ALA')
mutator.apply(self.pose)
xyz = self.get_atom_xyz(resid, "CB")
mutator = rosetta.protocols.simple_moves.MutateResidue(resid, 'GLY')
mutator.apply(self.pose)
return xyz
def resid_is_N_term(self, resid):
return self.pose.residue(resid).is_lower_terminus()
def resid_is_C_term(self, resid):
return self.pose.residue(resid).is_upper_terminus()
def resids_are_same_chain(self, resid1, resid2):
return self.pose.chain(resid1) == self.pose.chain(resid2)
[docs]class MDTrajPoseWrapper(WrappedPose):
"""
This wrapper takes a MDTraj trajectory and requires MDTraj to be installed
Parameters
---------
mdtraj_trajectory: Trajectory
Pose in MDTraj trajectory format
"""
def __init__(self, mdtraj_trajectory):
WrappedPose.__init__(self)
if md is None:
print("MDTrajPoseWrapper requires the mdtraj library to be installed")
raise ImportError
assert isinstance(mdtraj_trajectory, md.Trajectory)
assert mdtraj_trajectory.n_frames == 1
self.trajectory = mdtraj_trajectory
# RADIANS:
self.phi_atoms, self.phis = md.compute_phi(self.trajectory)
self.psi_atoms, self.psis = md.compute_psi(self.trajectory)
self.chis = [None, None, None, None, None] # Adding zero element just to make indexing easier
self.chi_atoms = [None, None, None, None, None]
self.chi_atoms[1], self.chis[1] = md.compute_chi1(self.trajectory)
self.chi_atoms[2], self.chis[2] = md.compute_chi2(self.trajectory)
self.chi_atoms[3], self.chis[3] = md.compute_chi3(self.trajectory)
self.chi_atoms[4], self.chis[4] = md.compute_chi4(self.trajectory)
def get_atom_xyz(self, resid, atomname):
atom = self.trajectory.topology.residue(resid - 1).atom(atomname)
all_xyzs = self.trajectory.xyz
return all_xyzs[0][atom.index] * 10 # nm -> Å
def _get_phi_or_psi_angle(self, resid, value_vec, atom_vec):
assert len(value_vec[0]) == len(atom_vec)
# value_vec.shape: (n_frames, n_phi)
# value_vec.shape: (n_phi, 4)
# Okay this is pretty inefficient
top = self.trajectory.topology
for i in range(0, len(value_vec[0])):
atom_index = atom_vec[i][2] # Last-Middle atom
atom = top.atom(atom_index)
if atom.residue.index == resid - 1:
return value_vec[0][i]
elif atom.residue.index > resid - 1:
return 0
return 0
def get_phi_psi(self, resid):
phi_rad = self._get_phi_or_psi_angle(resid, self.phis, self.phi_atoms)
psi_rad = self._get_phi_or_psi_angle(resid, self.psis, self.psi_atoms)
return [phi_rad, psi_rad]
def get_chi(self, resid, chi_number):
# DOESN'T GIVE PROTON CHIs
assert chi_number > 0
assert chi_number <= 4
# Okay this is pretty inefficient
top = self.trajectory.topology
for i in range(0, len(self.chi_atoms[chi_number])):
atom_index = self.chi_atoms[chi_number][i][3] # Last atom
atom = top.atom(atom_index)
if atom.residue.index == resid - 1:
return self.chis[chi_number][0][i], True
elif atom.residue.index > resid - 1:
return 0, False
return 0, False
def get_name1(self, resid):
return self.trajectory.topology.residue(resid - 1).code
def residues_are_polymer_bonded(self, resid1, resid2):
if not self.resids_are_same_chain(resid1, resid2):
return False
if abs(resid1 - resid2) > 1:
return False
first_res = min(resid1, resid2)
second_res = max(resid1, resid2)
top = self.trajectory.topology
C_atom_index = top.residue(first_res - 1).atom("C")
N_atom_index = top.residue(second_res - 1).atom("N")
for a1, a2 in top.bonds:
if (a1 == C_atom_index and a2 == N_atom_index) or (a2 == C_atom_index and a1 == N_atom_index):
return True
return False
def n_residues(self):
return self.trajectory.topology.n_residues
def resid_is_N_term(self, resid):
top = self.trajectory.topology
chain = top.residue(resid - 1).chain
N_term_resid = chain.residue(0)
return N_term_resid == resid - 1
def resid_is_C_term(self, resid):
top = self.trajectory.topology
chain = top.residue(resid - 1).chain
C_term_resid = chain.residue(chain.n_residues - 1)
return C_term_resid == resid - 1
def resids_are_same_chain(self, resid1, resid2):
top = self.trajectory.topology
return top.residue(resid1 - 1).chain.index == top.residue(resid2 - 1).chain.index