|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import torch,math |
|
from pyuul.sources.globalVariables import * |
|
|
|
import numpy as np |
|
import random |
|
|
|
def setup_seed(seed): |
|
torch.manual_seed(seed) |
|
torch.cuda.manual_seed_all(seed) |
|
np.random.seed(seed) |
|
random.seed(seed) |
|
torch.backends.cudnn.deterministic = True |
|
setup_seed(100) |
|
|
|
class Voxels(torch.nn.Module): |
|
|
|
def __init__(self, device=torch.device("cpu"),sparse=True): |
|
""" |
|
Constructor for the Voxels class, which builds the main PyUUL object. |
|
|
|
Parameters |
|
---------- |
|
|
|
device : torch.device |
|
The device on which the model should run. E.g. torch.device("cuda") or torch.device("cpu:0") |
|
sparse : bool |
|
Use sparse tensors calculation when possible |
|
|
|
Returns |
|
------- |
|
""" |
|
super(Voxels, self).__init__() |
|
|
|
self.sparse=sparse |
|
self.boxsize = None |
|
self.dev = device |
|
|
|
def __transform_coordinates(self,coords,radius=None): |
|
""" |
|
Private function that transform the coordinates to fit them in the 3d box. It also takes care of the resolution. |
|
|
|
Parameters |
|
---------- |
|
coords : torch.Tensor |
|
Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ) |
|
radius : torch.Tensor or None |
|
Radius of the atoms. Shape ( batch, numberOfAtoms ) |
|
|
|
Returns |
|
------- |
|
coords : torch.Tensor |
|
transformed coordinates |
|
|
|
""" |
|
coords = (coords*self.dilatation)- self.translation |
|
if not radius is None: |
|
radius = radius*self.dilatation |
|
return coords,radius |
|
else: |
|
return coords |
|
''' |
|
def get_coords_voxel(self, voxel_indices, resolution): |
|
""" |
|
returns the coordinates of the center of the voxel provided its indices. |
|
|
|
Parameters |
|
---------- |
|
voxel_indices : torch.Tensor |
|
Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ) |
|
resolution : torch.Tensor or None |
|
Radius of the atoms. Shape ( batch, numberOfAtoms ) |
|
|
|
Returns |
|
------- |
|
""" |
|
#voxel_indices is a n,3 long tensor |
|
centersCoords = voxel_indices + 0.5*resolution |
|
return (centersCoords + self.translation)/self.dilatation |
|
''' |
|
def __define_spatial_conformation(self,mincoords,cubes_around_atoms_dim,resolution): |
|
""" |
|
Private function that defines the space of the volume. Takes resolution and margins into consideration. |
|
|
|
Parameters |
|
---------- |
|
mincoords : torch.Tensor |
|
minimum coordinates of each macromolecule of the batch. Shape ( batch, 3 ) |
|
cubes_around_atoms_dim : int |
|
maximum distance in number of voxels to check for atom contribution to occupancy of a voxel |
|
resolution : float |
|
side in A of a voxel. The lower this value is the higher the resolution of the final representation will be |
|
Returns |
|
------- |
|
""" |
|
self.translation=(mincoords-(cubes_around_atoms_dim)).unsqueeze(1) |
|
self.dilatation = 1.0/resolution |
|
|
|
''' |
|
def find_cubes_indices(self,coords): |
|
coords_scaled = self.transform_coordinates(coords) |
|
return torch.trunc(coords_scaled.data).long() |
|
''' |
|
|
|
def forward( self,coords, radius,channels,numberchannels=None,resolution=1, cubes_around_atoms_dim=5, steepness=10,function="sigmoid"): |
|
""" |
|
Voxels representation of the macromolecules |
|
|
|
Parameters |
|
---------- |
|
coords : torch.Tensor |
|
Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ). Can be calculated from a PDB file using utils.parsePDB |
|
radius : torch.Tensor |
|
Radius of the atoms. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToRadius |
|
channels: torch.LongTensor |
|
channels of the atoms. Atoms of the same type shold belong to the same channel. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToChannels |
|
numberchannels : int or None |
|
maximum number of channels. if None, max(atNameHashing) + 1 is used |
|
|
|
cubes_around_atoms_dim : int |
|
maximum distance in number of voxels for which the contribution to occupancy is taken into consideration. Every atom that is farer than cubes_around_atoms_dim voxels from the center of a voxel does no give any contribution to the relative voxel occupancy |
|
resolution : float |
|
side in A of a voxel. The lower this value is the higher the resolution of the final representation will be |
|
|
|
steepness : float or int |
|
steepness of the sigmoid occupancy function. |
|
|
|
function : "sigmoid" or "gaussian" |
|
occupancy function to use. Can be sigmoid (every atom has a sigmoid shaped occupancy function) or gaussian (based on Li et al. 2014) |
|
Returns |
|
------- |
|
volume : torch.Tensor |
|
voxel representation of the macromolecules in the batch. Shape ( batch, channels, x,y,z), where x,y,z are the size of the 3D volume in which the macromolecules have been represented |
|
|
|
""" |
|
padding_mask = ~channels.eq(PADDING_INDEX) |
|
if numberchannels is None: |
|
numberchannels = int(channels[padding_mask].max().cpu().data+1) |
|
self.featureVectorSize = numberchannels |
|
self.function = function |
|
|
|
arange_type = torch.int16 |
|
|
|
gx = torch.arange(-cubes_around_atoms_dim, cubes_around_atoms_dim + 1, device=self.dev, dtype=arange_type) |
|
gy = torch.arange(-cubes_around_atoms_dim, cubes_around_atoms_dim + 1, device=self.dev, dtype=arange_type) |
|
gz = torch.arange(-cubes_around_atoms_dim, cubes_around_atoms_dim + 1, device=self.dev, dtype=arange_type) |
|
self.lato = gx.shape[0] |
|
|
|
x1 = gx.unsqueeze(1).expand(self.lato, self.lato).unsqueeze(-1) |
|
x2 = gy.unsqueeze(0).expand(self.lato, self.lato).unsqueeze(-1) |
|
|
|
xy = torch.cat([x1, x2], dim=-1).unsqueeze(2).expand(self.lato, self.lato, self.lato, 2) |
|
x3 = gz.unsqueeze(0).unsqueeze(1).expand(self.lato, self.lato, self.lato).unsqueeze(-1) |
|
|
|
del gx, gy, gz, x1, x2 |
|
|
|
self.standard_cube = torch.cat([xy, x3], dim=-1).unsqueeze(0).unsqueeze(0) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mincoords = torch.min(coords[:, :, :], dim=1)[0] |
|
mincoords = torch.trunc(mincoords / resolution) |
|
|
|
|
|
box_size_x = (math.ceil(torch.max(coords[padding_mask][:,0])/resolution)-mincoords[:,0].min())+(2*cubes_around_atoms_dim+1) |
|
box_size_y = (math.ceil(torch.max(coords[padding_mask][:,1])/resolution)-mincoords[:,1].min())+(2*cubes_around_atoms_dim+1) |
|
box_size_z = (math.ceil(torch.max(coords[padding_mask][:,2])/resolution)-mincoords[:,2].min())+(2*cubes_around_atoms_dim+1) |
|
|
|
|
|
self.__define_spatial_conformation(mincoords,cubes_around_atoms_dim,resolution) |
|
coords,radius = self.__transform_coordinates(coords,radius) |
|
|
|
boxsize = (int(box_size_x),int(box_size_y),int(box_size_z)) |
|
self.boxsize=boxsize |
|
|
|
|
|
if max(boxsize)<256: |
|
self.dtype_indices=torch.uint8 |
|
else: |
|
self.dtype_indices = torch.int16 |
|
|
|
if self.function=="sigmoid": |
|
volume = self.__forward_actual_calculation(coords, boxsize, radius, channels,padding_mask,steepness,resolution) |
|
elif self.function=="gaussian": |
|
volume = self.__forward_actual_calculationGaussian(coords, boxsize, radius, channels, padding_mask,resolution) |
|
return volume |
|
|
|
def __forward_actual_calculationGaussian(self, coords_scaled, boxsize, radius, atNameHashing, padding_mask,resolution): |
|
""" |
|
private function for the calculation of the gaussian voxel occupancy |
|
|
|
Parameters |
|
---------- |
|
coords_scaled : torch.LongTensor |
|
Discrete Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ) |
|
boxsize : torch.LongTensor |
|
The size of the box in which the macromolecules are represented |
|
radius : torch.Tensor |
|
Radius of the atoms. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToRadius |
|
atNameHashing: torch.LongTensor |
|
channels of the atoms. Atoms of the same type shold belong to the same channel. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToChannels |
|
resolution : float |
|
side in A of a voxel. The lower this value is the higher the resolution of the final representation will be |
|
padding_mask : torch.BoolTensor |
|
tensor to mask the padding. Shape (batch, numberOfAtoms) |
|
Returns |
|
------- |
|
volume : torch.Tensor |
|
voxel representation of the macromolecules in the batch with Gaussian occupancy function. Shape ( batch, channels, x,y,z), where x,y,z are the size of the 3D volume in which the macromolecules have been represented |
|
|
|
""" |
|
batch = coords_scaled.shape[0] |
|
dev = self.dev |
|
L = coords_scaled.shape[1] |
|
|
|
discrete_coordinates = torch.trunc(coords_scaled.data).to(self.dtype_indices) |
|
|
|
|
|
|
|
|
|
radius = radius.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
|
atNameHashing = atNameHashing.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
|
coords_scaled = coords_scaled.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
|
discrete_coordinates = discrete_coordinates.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
|
distmat_standard_cube = torch.norm( |
|
coords_scaled - ((discrete_coordinates + self.standard_cube + 1) + 0.5 * resolution), dim=-1).to( |
|
coords_scaled.dtype) |
|
|
|
atNameHashing = atNameHashing.long() |
|
|
|
''' |
|
exponent = self.steepness*(distmat_standard_cube-radius) |
|
|
|
exp_mask = exponent.ge(10) |
|
exponent = torch.masked_fill(exponent,exp_mask, 10) |
|
|
|
volume_cubes = 1.0/(1.0+torch.exp(exponent)) |
|
''' |
|
|
|
sigma = 0.93 |
|
exponent = -distmat_standard_cube[padding_mask] ** 2 / (sigma ** 2 * radius[padding_mask] ** 2) |
|
exp_mask = exponent.ge(10) |
|
exponent = torch.masked_fill(exponent, exp_mask, 10) |
|
volume_cubes = torch.exp(exponent) |
|
|
|
|
|
batch_list = torch.arange(batch,device=dev).unsqueeze(1).unsqueeze(1).unsqueeze(1).unsqueeze(1).expand(batch,L,self.lato,self.lato,self.lato) |
|
|
|
cubes_coords = (discrete_coordinates[padding_mask] + self.standard_cube.squeeze(0) + 1)[~exp_mask] |
|
atNameHashing = atNameHashing[padding_mask].expand(-1,self.lato,self.lato,self.lato) |
|
if self.sparse: |
|
|
|
index_tens = torch.cat( |
|
[batch_list[padding_mask][~exp_mask].view(-1).unsqueeze(0), |
|
atNameHashing[~exp_mask].unsqueeze(0), |
|
cubes_coords[:,0].unsqueeze(0), |
|
cubes_coords[:,1].unsqueeze(0), |
|
cubes_coords[:,2].unsqueeze(0), |
|
]) |
|
|
|
|
|
volume_cubes = volume_cubes[~exp_mask].view(-1) |
|
volume_cubes = torch.log(1 - volume_cubes.contiguous()) |
|
|
|
volume = torch.sparse_coo_tensor(indices=index_tens, values=volume_cubes.exp(), size=[batch, self.featureVectorSize, boxsize[0] , boxsize[1] , boxsize[2] ]).coalesce() |
|
volume = torch.sparse_coo_tensor(volume.indices(),1 - volume.values(), volume.shape) |
|
|
|
else: |
|
volume = torch.zeros(batch,boxsize[0]+1,boxsize[1]+1,boxsize[2]+1,self.featureVectorSize,device=dev,dtype=torch.float) |
|
|
|
index = (batch_list[padding_mask][~exp_mask].view(-1).long(), |
|
cubes_coords[:,0].long(), |
|
cubes_coords[:,1].long(), |
|
cubes_coords[:,2].long(), |
|
atNameHashing[~exp_mask]) |
|
volume_cubes=volume_cubes[~exp_mask].view(-1) |
|
|
|
volume_cubes = torch.log(1 - volume_cubes.contiguous()) |
|
volume = 1- torch.exp(volume.index_put(index,volume_cubes,accumulate=True)) |
|
|
|
volume=volume.permute(0,4,1,2,3) |
|
|
|
|
|
return volume |
|
|
|
|
|
|
|
return volume |
|
|
|
def __sparseClamp(self,volume, minv, maxv): |
|
vals = volume.values() |
|
ind = volume.indices() |
|
|
|
vals = vals.clamp(minv, maxv) |
|
volume = torch.sparse_coo_tensor(indices=ind, values=vals, size=volume.shape).coalesce() |
|
return volume |
|
|
|
def __forward_actual_calculation(self, coords_scaled, boxsize, radius,atNameHashing,padding_mask,steepness,resolution): |
|
""" |
|
private function for the calculation of the gaussian voxel occupancy |
|
|
|
Parameters |
|
---------- |
|
coords_scaled : torch.LongTensor |
|
Discrete Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ) |
|
boxsize : torch.LongTensor |
|
The size of the box in which the macromolecules are represented |
|
radius : torch.Tensor |
|
Radius of the atoms. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToRadius |
|
atNameHashing: torch.LongTensor |
|
channels of the atoms. Atoms of the same type shold belong to the same channel. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToChannels |
|
resolution : float |
|
side in A of a voxel. The lower this value is the higher the resolution of the final representation will be |
|
padding_mask : torch.BoolTensor |
|
tensor to mask the padding. Shape (batch, numberOfAtoms) |
|
steepness : float |
|
steepness of the sigmoid function (coefficient of the exponent) |
|
|
|
Returns |
|
------- |
|
volume : torch.Tensor |
|
voxel representation of the macromolecules in the batch with Sigmoid occupancy function. Shape ( batch, channels, x,y,z), where x,y,z are the size of the 3D volume in which the macromolecules have been represented |
|
|
|
""" |
|
batch = coords_scaled.shape[0] |
|
dev=self.dev |
|
L = coords_scaled.shape[1] |
|
|
|
discrete_coordinates = torch.trunc(coords_scaled.data).to(self.dtype_indices) |
|
|
|
|
|
|
|
|
|
radius = radius.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
|
atNameHashing = atNameHashing.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
|
coords_scaled = coords_scaled.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
|
discrete_coordinates = discrete_coordinates.unsqueeze(2).unsqueeze(3).unsqueeze(4) |
|
distmat_standard_cube = torch.norm(coords_scaled-((discrete_coordinates + self.standard_cube + 1) + 0.5 * resolution), dim=-1).to(coords_scaled.dtype) |
|
|
|
atNameHashing = atNameHashing.long() |
|
|
|
exponent = steepness*(distmat_standard_cube[padding_mask]-radius[padding_mask]) |
|
del distmat_standard_cube |
|
exp_mask = exponent.ge(10) |
|
exponent = torch.masked_fill(exponent,exp_mask, 10) |
|
|
|
volume_cubes = 1.0/(1.0+torch.exp(exponent)) |
|
|
|
|
|
batch_list = torch.arange(batch,device=dev).unsqueeze(1).unsqueeze(1).unsqueeze(1).unsqueeze(1).expand(batch,L,self.lato,self.lato,self.lato) |
|
|
|
|
|
cubes_coords = (discrete_coordinates[padding_mask] + self.standard_cube.squeeze(0) + 1)[~exp_mask] |
|
atNameHashing = atNameHashing[padding_mask].expand(-1,self.lato,self.lato,self.lato) |
|
if self.sparse: |
|
|
|
index_tens = torch.cat( |
|
[batch_list[padding_mask][~exp_mask].view(-1).unsqueeze(0), |
|
atNameHashing[~exp_mask].unsqueeze(0), |
|
cubes_coords[:,0].unsqueeze(0), |
|
cubes_coords[:,1].unsqueeze(0), |
|
cubes_coords[:,2].unsqueeze(0), |
|
]) |
|
|
|
volume = torch.sparse_coo_tensor(indices=index_tens, values=volume_cubes[~exp_mask].view(-1), size=[batch, self.featureVectorSize, boxsize[0] , boxsize[1] , boxsize[2] ]).coalesce() |
|
volume = self.__sparseClamp(volume,0,1) |
|
|
|
else: |
|
volume = torch.zeros(batch,boxsize[0]+1,boxsize[1]+1,boxsize[2]+1,self.featureVectorSize,device=dev,dtype=torch.float) |
|
|
|
index = (batch_list[padding_mask][~exp_mask].view(-1).long(), |
|
cubes_coords[:,0].long(), |
|
cubes_coords[:,1].long(), |
|
cubes_coords[:,2].long(), |
|
atNameHashing[~exp_mask]) |
|
volume_cubes=volume_cubes[~exp_mask].view(-1) |
|
|
|
volume = volume.index_put(index,volume_cubes.view(-1),accumulate=True) |
|
|
|
volume = -torch.nn.functional.threshold(-volume,-1,-1) |
|
volume = volume.permute(0,4,1,2,3) |
|
|
|
return volume |
|
''' |
|
mesh will be added as soon as pytorch3d becomes a little more stable |
|
def mesh(self,coords, radius,threshSurface = 0.01): |
|
|
|
atNameHashing= torch.zeros(radius.shape).to(self.dev) |
|
mask = radius.eq(PADDING_INDEX) |
|
atNameHashing = atNameHashing.masked_fill_(mask,PADDING_INDEX) |
|
vol = self(coords,radius,atNameHashing).to_dense() |
|
mesh = cubifyNOALIGN(vol.sum(-1),thresh=threshSurface)# creates pytorch 3d mesh from cubes. It uses a MODIFIED version of pytorch3d with no align |
|
return mesh |
|
''' |
|
|
|
class PointCloudSurface(torch.nn.Module): |
|
def __init__(self,device="cpu"): |
|
""" |
|
Constructor for the CloudPointSurface class, which builds the main PyUUL object for cloud surface. |
|
|
|
Parameters |
|
---------- |
|
device : torch.device |
|
The device on which the model should run. E.g. torch.device("cuda") or torch.device("cpu:0") |
|
|
|
|
|
Returns |
|
------- |
|
""" |
|
super(PointCloudSurface, self).__init__() |
|
|
|
self.device=device |
|
|
|
def __buildStandardSphere(self,npoints=50): |
|
|
|
goldenRatio = (1 + 5 ** 0.5) / 2 |
|
i = torch.arange(0, npoints,device=self.device) |
|
theta = 2 * math.pi * i / goldenRatio |
|
phi = torch.acos(1 - 2 * (i + 0.5) / npoints) |
|
|
|
x, y, z = torch.cos(theta) * torch.sin(phi), torch.sin(theta) * torch.sin(phi), torch.cos(phi) |
|
|
|
coords=torch.cat([x.unsqueeze(-1),y.unsqueeze(-1),z.unsqueeze(-1)],dim=-1) |
|
|
|
|
|
return coords |
|
|
|
def forward(self, coords, radius, maxpoints=5000,external_radius_factor=1.4): |
|
""" |
|
Function to calculate the surface cloud point representation of macromolecules |
|
|
|
Parameters |
|
---------- |
|
coords : torch.Tensor |
|
Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ). Can be calculated from a PDB file using utils.parsePDB |
|
radius : torch.Tensor |
|
Radius of the atoms. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToRadius |
|
maxpoints : int |
|
number of points per macromolecule in the batch |
|
external_radius_factor=1.4 |
|
multiplicative factor of the radius in order ot define the place to sample the points around each atom. The higher this value is, the smoother the surface will be |
|
Returns |
|
------- |
|
surfacePointCloud : torch.Tensor |
|
surface point cloud representation of the macromolecules in the batch. Shape ( batch, channels, numberOfAtoms, 3) |
|
|
|
""" |
|
padding_mask = ~radius.eq(PADDING_INDEX) |
|
|
|
batch = coords.shape[0] |
|
npoints = torch.div(maxpoints,(padding_mask.sum(-1).min() + 1), rounding_mode="floor") * 2 |
|
|
|
sphere = self.__buildStandardSphere(npoints) |
|
finalPoints=[] |
|
|
|
for b in range(batch): |
|
|
|
distmat = torch.cdist(coords[b][padding_mask[b]].unsqueeze(0), coords[b][padding_mask[b]].unsqueeze(0)) |
|
L=distmat.shape[1] |
|
AtomSelfContributionMask = torch.eye(L, dtype=torch.bool, device=self.device).unsqueeze(0) |
|
triangular_mask = ~torch.tril(torch.ones((L, L), dtype=torch.bool, device=self.device), diagonal=-1).unsqueeze(0) |
|
|
|
|
|
external_radius = radius * external_radius_factor |
|
todoMask = (distmat[0].le(5) & (~AtomSelfContributionMask)).squeeze(0) |
|
points = coords[b][padding_mask[b]].unsqueeze(0).unsqueeze(-2) - sphere.unsqueeze(0).unsqueeze(1) * external_radius[b][padding_mask[b]].unsqueeze(0).unsqueeze(-1).unsqueeze(-1) |
|
|
|
p = points.expand( L, L, npoints, 3)[todoMask] |
|
c = coords[b][padding_mask[b]].unsqueeze(1).unsqueeze(-2).expand( L, L, points.shape[2], 3)[todoMask] |
|
r = radius[b][padding_mask[b]].unsqueeze(1).unsqueeze(-2).expand( L, L, points.shape[2])[todoMask] |
|
occupancy = self.__occupancy(p, c, r) |
|
|
|
point_index = torch.arange(0,L*npoints,device=self.device).view(L,npoints).unsqueeze(0).expand(L,L,npoints)[todoMask] |
|
point_occupancy =torch.zeros((L*npoints),dtype=torch.float,device=self.device) |
|
point_occupancy = point_occupancy.index_put_([point_index.view(-1)], occupancy.view(-1), accumulate=True) |
|
point_occupancy = (1- torch.exp(point_occupancy)) |
|
|
|
points_on_surfaceMask = point_occupancy.le(0.5) |
|
|
|
points=points.permute(0,3,1,2).view(3,-1).transpose(0,1)[points_on_surfaceMask] |
|
random_indices = torch.randint(0, points.shape[0], [maxpoints], device=self.device) |
|
sampled_points = points[random_indices,:] |
|
|
|
finalPoints +=[sampled_points] |
|
|
|
return torch.cat(finalPoints,dim=0) |
|
|
|
def __occupancy(self, points, coords, radius): |
|
|
|
dist = torch.norm(points-coords,dim=-1) |
|
|
|
|
|
sigma=0.93 |
|
exponent = -dist**2/(sigma**2 * radius**2) |
|
exp_mask = exponent.ge(10) |
|
exponent = torch.masked_fill(exponent, exp_mask, 10) |
|
|
|
occupancy_on_points = torch.exp(exponent) |
|
return torch.log(1-occupancy_on_points) |
|
return occupancy_on_points |
|
del exponent |
|
|
|
AtomSelfContributionMask = torch.eye(L,dtype=torch.bool,device=self.device).unsqueeze(0).expand(batch,L,L) |
|
occupancy_on_points[AtomSelfContributionMask]=0.0 |
|
|
|
occupancy = (1-torch.exp(torch.log(1-occupancy_on_points).sum(2))) |
|
|
|
|
|
return occupancy |
|
|
|
class PointCloudVolume(torch.nn.Module): |
|
def __init__(self, device="cpu"): |
|
""" |
|
Constructor for the CloudPointSurface class, which builds the main PyUUL object for volumetric point cloud. |
|
|
|
Parameters |
|
---------- |
|
device : torch.device |
|
The device on which the model should run. E.g. torch.device("cuda") or torch.device("cpu:0") |
|
|
|
|
|
Returns |
|
------- |
|
""" |
|
super(PointCloudVolume, self).__init__() |
|
|
|
self.device = device |
|
|
|
def forward(self, coords, radius, maxpoints=500): |
|
|
|
""" |
|
Function to calculate the volumetric cloud point representation of macromolecules |
|
|
|
Parameters |
|
---------- |
|
coords : torch.Tensor |
|
Coordinates of the atoms. Shape ( batch, numberOfAtoms, 3 ). Can be calculated from a PDB file using utils.parsePDB |
|
radius : torch.Tensor |
|
Radius of the atoms. Shape ( batch, numberOfAtoms ). Can be calculated from a PDB file using utils.parsePDB and utils.atomlistToRadius |
|
maxpoints : int |
|
number of points per macromolecule in the batch |
|
|
|
Returns |
|
------- |
|
PointCloudVolume : torch.Tensor |
|
volume point cloud representation of the macromolecules in the batch. Shape ( batch, channels, numberOfAtoms, 3) |
|
|
|
""" |
|
|
|
padding_mask = ~radius.eq(PADDING_INDEX) |
|
|
|
|
|
|
|
batch = coords.shape[0] |
|
L = coords.shape[1] |
|
|
|
batched = [] |
|
for i in range(batch): |
|
mean = coords[i][padding_mask[i]] |
|
|
|
sampled = radius[i][padding_mask[i]].sqrt().unsqueeze(-1) * torch.randn((mean.size()), device=self.device) + mean |
|
p = sampled.view(-1,3) |
|
random_indices = torch.randint(0, p.shape[0], [maxpoints], device=self.device) |
|
batched+=[p[random_indices].unsqueeze(0)] |
|
|
|
batched = torch.cat(batched,dim=0) |
|
return batched |
|
|
|
|