Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

calculate atom pairwise energies (combination between LJ and Coulomb) using OpenMM #501

Closed
DaniBodor opened this issue Sep 21, 2023 · 6 comments
Assignees
Labels
feature New feature to add stale issue not touched from too much time

Comments

@DaniBodor
Copy link
Collaborator

No description provided.

@DaniBodor DaniBodor added the feature New feature to add label Sep 21, 2023
@DaniBodor
Copy link
Collaborator Author

DaniBodor commented Sep 21, 2023

Currently the definition of the interface is a bit of a mess (see #460), as we don't differentiate between the distance cutoff between graph nodes to create an edge and distance cutoff of residues to the interface to include in the graph. This will change in PR #492 that I am currently working on. Either way, it's a parameter that can be set by user and is passed to these functions:

def build_atomic_graph( # pylint: disable=too-many-locals
atoms: List[Atom], graph_id: str, edge_distance_cutoff: float
) -> Graph:
"""Builds a graph, using the atoms as nodes.
The edge distance cutoff is in Ångströms.
"""
positions = np.empty((len(atoms), 3))
for atom_index, atom in enumerate(atoms):
positions[atom_index] = atom.position
distances = distance_matrix(positions, positions, p=2)
neighbours = distances < edge_distance_cutoff
graph = Graph(graph_id, edge_distance_cutoff)
for atom1_index, atom2_index in np.transpose(np.nonzero(neighbours)):
if atom1_index != atom2_index:
atom1 = atoms[atom1_index]
atom2 = atoms[atom2_index]
contact = AtomicContact(atom1, atom2)
node1 = Node(atom1)
node2 = Node(atom2)
node1.features[Nfeat.POSITION] = atom1.position
node2.features[Nfeat.POSITION] = atom2.position
graph.add_node(node1)
graph.add_node(node2)
graph.add_edge(Edge(contact))
return graph
def build_residue_graph( # pylint: disable=too-many-locals
residues: List[Residue], graph_id: str, edge_distance_cutoff: float
) -> Graph:
"""Builds a graph, using the residues as nodes.
The edge distance cutoff is in Ångströms.
It's the shortest interatomic distance between two residues.
"""
# collect the set of atoms and remember which are on the same residue (by index)
atoms = []
atoms_residues = []
for residue_index, residue in enumerate(residues):
for atom in residue.atoms:
atoms.append(atom)
atoms_residues.append(residue_index)
atoms_residues = np.array(atoms_residues)
# calculate the distance matrix
positions = np.empty((len(atoms), 3))
for atom_index, atom in enumerate(atoms):
positions[atom_index] = atom.position
distances = distance_matrix(positions, positions, p=2)
# determine which atoms are close enough
neighbours = distances < edge_distance_cutoff
atom_index_pairs = np.transpose(np.nonzero(neighbours))
# point out the unique residues for the atom pairs
residue_index_pairs = np.unique(atoms_residues[atom_index_pairs], axis=0)
# build the graph
graph = Graph(graph_id, edge_distance_cutoff)
for residue1_index, residue2_index in residue_index_pairs:
residue1: Residue = residues[residue1_index]
residue2: Residue = residues[residue2_index]
if residue1 != residue2:
contact = ResidueContact(residue1, residue2)
node1 = Node(residue1)
node2 = Node(residue2)
edge = Edge(contact)
node1.features[Nfeat.POSITION] = residue1.get_center()
node2.features[Nfeat.POSITION] = residue2.get_center()
# The same residue will be added multiple times as a node,
# but the Graph class fixes this.
graph.add_node(node1)
graph.add_node(node2)
graph.add_edge(edge)
return graph

The atom pairs are defined by this function:

def get_contact_atoms( # pylint: disable=too-many-locals
pdb_path: str,
chain_id1: str,
chain_id2: str,
distance_cutoff: float
) -> List[Atom]:
"""Gets the contact atoms from pdb2sql and wraps them in python objects."""
interface = get_interface(pdb_path)
try:
atom_indexes = interface.get_contact_atoms(cutoff=distance_cutoff, chain1=chain_id1, chain2=chain_id2)
rows = interface.get("x,y,z,name,element,altLoc,occ,chainID,resSeq,resName,iCode",
rowID=atom_indexes[chain_id1] + atom_indexes[chain_id2])
finally:
interface._close() # pylint: disable=protected-access
pdb_name = os.path.splitext(os.path.basename(pdb_path))[0]
structure = PDBStructure(f"contact_atoms_{pdb_name}")
for row in rows:
(
x,
y,
z,
atom_name,
element_name,
altloc,
occupancy,
chain_id,
residue_number,
residue_name,
insertion_code
) = row
_add_atom_data_to_structure(structure,
x, y, z,
atom_name,
altloc, occupancy,
element_name,
chain_id,
residue_number,
residue_name,
insertion_code)
return structure.get_atoms()

@DaniBodor
Copy link
Collaborator Author

As @cbaakman pointed out, the current feature module that calculates the nonbond energies in the "old" method is this: https://github.com/DeepRank/deeprank2/blob/main/deeprank2/features/contact.py

@joaomcteixeira
Copy link

Hi,

I see. Following the conversations on Slack, I am making the script to receive the PDB with the complex as input. Then, I calculate the interface residues by a heavy-atom distance cutoff of 5A (parameter that can be changed). Finally, it outputs a list with the atom's description, like the one below. However, I can make a function that also receives a list of residue pairs if you wish to input that instead of the complex. The same goes for the output; I can make it in any format. There are additional functionalities that I am obviating now for simplicity.

Whatever the case, I will make it a standalone script that can be used as a command-line executable or a library from which you can import the functions you may need in other contexts.

chain A PRO 21 CA chain B ALA 46 C -0.04088635383668866 -1.089472957596496
chain A PRO 21 CA chain B ALA 46 O -0.12798309611088915 1.292682628558653
chain A PRO 21 CA chain B ALA 46 CB -0.06765552420689089 0.35987891271055084
chain A PRO 21 C chain B ALA 46 CA -0.03856243156110351 1.347274101031214

@github-actions
Copy link

This issue is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale issue not touched from too much time label Oct 23, 2023
@DaniBodor DaniBodor removed the stale issue not touched from too much time label Oct 31, 2023
Copy link

github-actions bot commented Dec 1, 2023

This issue is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale issue not touched from too much time label Dec 1, 2023
@gcroci2
Copy link
Collaborator

gcroci2 commented Jun 27, 2024

This should be done in pdbprep, and its integration into deeprank2 will be handled in #509

@gcroci2 gcroci2 closed this as completed Jun 27, 2024
@gcroci2 gcroci2 moved this to Done in Development Jul 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature to add stale issue not touched from too much time
Projects
Status: Done
Development

No branches or pull requests

3 participants