mirror of
https://github.com/rdkit/rdkit.git
synced 2026-06-05 22:04:27 +08:00
* fix for ligand only DCLV calcs * formatting suggestion from review --------- Co-authored-by: Greg Landrum <greg.landrum@gmail.com>
889 lines
25 KiB
C++
889 lines
25 KiB
C++
/*=================================================================*/
|
|
/* Copyright (C) 2024 Greg Landrum and other RDKit contributors */
|
|
/* Contributed by NextMove Software, Cambridge, UK. */
|
|
/* */
|
|
/* */
|
|
/* @@ All Rights Reserved @@ */
|
|
/* The contents are covered by the terms of the */
|
|
/* BSD license, which is included in the file */
|
|
/* license.txt. */
|
|
/*=================================================================*/
|
|
#define _USE_MATH_DEFINES
|
|
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <list>
|
|
#include <cmath>
|
|
|
|
#include <GraphMol/GraphMol.h>
|
|
#include <GraphMol/MolOps.h>
|
|
#include <GraphMol/MonomerInfo.h>
|
|
#include <Geometry/point.h>
|
|
#include <GraphMol/RDKitBase.h>
|
|
#include <GraphMol/FileParsers/FileParsers.h>
|
|
|
|
#include "DCLV.h"
|
|
|
|
using RDGeom::Point3D;
|
|
|
|
namespace RDKit {
|
|
namespace Descriptors {
|
|
|
|
constexpr int VOXORDER = 16;
|
|
constexpr int CHECKMAX = 20;
|
|
constexpr int ATOMPOOL = 16;
|
|
constexpr int maxDepth = 8;
|
|
constexpr int maxDensity = 1000;
|
|
|
|
constexpr unsigned int HetAtmFlag = 0x01;
|
|
constexpr unsigned int WaterFlag = 0x10;
|
|
|
|
struct CheckType {
|
|
const char* name;
|
|
const unsigned int count;
|
|
};
|
|
|
|
static const CheckType residueCheck[CHECKMAX] = {
|
|
{"ALA", 5}, /* 0 */
|
|
{"GLY", 4}, /* 1 */
|
|
{"LEU", 8}, /* 2 */
|
|
{"SER", 6}, /* 3 */
|
|
{"VAL", 7}, /* 4 */
|
|
{"THR", 7}, /* 5 */
|
|
{"LYS", 9}, /* 6 */
|
|
{"ASP", 8}, /* 7 */
|
|
{"ILE", 8}, /* 8 */
|
|
{"ASN", 8}, /* 9 */
|
|
{"GLU", 9}, /* 10 */
|
|
{"PRO", 7}, /* 11 */
|
|
{"ARG", 11}, /* 12 */
|
|
{"PHE", 11}, /* 13 */
|
|
{"GLN", 9}, /* 14 */
|
|
{"TYR", 12}, /* 15 */
|
|
{"HIS", 10}, /* 16 */
|
|
{"CYS", 6}, /* 17 */
|
|
{"MET", 8}, /* 18 */
|
|
{"TRP", 14} /* 19 */
|
|
};
|
|
|
|
struct DotStruct {
|
|
Point3D v;
|
|
double area;
|
|
};
|
|
|
|
struct ElemStruct {
|
|
std::vector<DotStruct> dots;
|
|
double radius2;
|
|
double radius;
|
|
long count;
|
|
};
|
|
|
|
class AtomRecord {
|
|
public:
|
|
float radius; /* VdW */
|
|
unsigned short atmSerNo;
|
|
unsigned short resSerNo;
|
|
float bFactor;
|
|
Point3D pos;
|
|
std::string atmName;
|
|
std::string resName;
|
|
std::string insert;
|
|
std::string chain;
|
|
int hetAtmFlag;
|
|
bool solventFlag;
|
|
char flag;
|
|
ElemStruct* elem;
|
|
|
|
bool isSolvent() {
|
|
switch (resName[0]) {
|
|
case 'D':
|
|
return resName == "DOD" || resName == "D20";
|
|
|
|
case 'H':
|
|
return resName == "HOH" || resName == "H20";
|
|
|
|
case 'S':
|
|
return resName == "SOL" || resName == "SO4" || resName == "SUL";
|
|
|
|
case 'W':
|
|
return resName == "WAT";
|
|
case 'T':
|
|
return resName == "TIP";
|
|
case 'P':
|
|
return resName == "P04";
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void initFlag() {
|
|
flag = 0; // initialise flag
|
|
if (isSolvent()) {
|
|
flag |= HetAtmFlag | WaterFlag;
|
|
} else if (hetAtmFlag) {
|
|
flag |= HetAtmFlag;
|
|
}
|
|
};
|
|
|
|
// constructor to populate record
|
|
AtomRecord(const Atom& atm, const Conformer cnf) {
|
|
const AtomMonomerInfo* info = atm.getMonomerInfo();
|
|
unsigned int i;
|
|
solventFlag = false;
|
|
|
|
if (info) {
|
|
atmName = info->getName();
|
|
resName = ((AtomPDBResidueInfo*)info)->getResidueName();
|
|
|
|
if (isSolvent()) {
|
|
solventFlag = true;
|
|
return;
|
|
}
|
|
|
|
atmSerNo = ((AtomPDBResidueInfo*)info)->getSerialNumber();
|
|
resSerNo = ((AtomPDBResidueInfo*)info)->getResidueNumber();
|
|
pos = cnf.getAtomPos(atm.getIdx());
|
|
insert = ((AtomPDBResidueInfo*)info)->getInsertionCode();
|
|
chain = ((AtomPDBResidueInfo*)info)->getChainId();
|
|
|
|
hetAtmFlag = 1;
|
|
for (i = 0; i < CHECKMAX; i++) {
|
|
if (residueCheck[i].name == resName) {
|
|
hetAtmFlag = 0;
|
|
}
|
|
}
|
|
} else { // Molecule not from PDB (Ligand only)
|
|
atmName = " " + atm.getSymbol();
|
|
resName = "UNK";
|
|
|
|
if (isSolvent()) {
|
|
solventFlag = true;
|
|
return;
|
|
}
|
|
|
|
atmSerNo = 1 + atm.getIdx();
|
|
resSerNo = 0;
|
|
pos = cnf.getAtomPos(atm.getIdx());
|
|
hetAtmFlag = 1;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct AtomList {
|
|
const AtomRecord* ptr[ATOMPOOL];
|
|
AtomList* next;
|
|
unsigned int count;
|
|
|
|
AtomList() : next(nullptr) {}
|
|
};
|
|
|
|
static void normalise(double* v) {
|
|
double len = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
v[0] /= len;
|
|
v[1] /= len;
|
|
v[2] /= len;
|
|
}
|
|
|
|
static double triangleArea(const Point3D& p, const Point3D& q,
|
|
const Point3D& r) {
|
|
auto a = q - p;
|
|
auto b = r - p;
|
|
auto c = a.crossProduct(b);
|
|
return (0.5 * c.length());
|
|
}
|
|
|
|
static int within(const AtomRecord& src, const AtomRecord* dst, double dist) {
|
|
return (src.pos - dst->pos).lengthSq() < dist * dist;
|
|
}
|
|
|
|
static void checkResidue(const AtomRecord* ptr, unsigned int count) {
|
|
for (unsigned int i = 0; i < CHECKMAX; i++) {
|
|
if (ptr->resName.compare(0, 3, residueCheck[i].name, 0, 3) == 0) {
|
|
if (residueCheck[i].count != count) {
|
|
std::cout << "Warning: Atom Count for residue " + ptr->resName +
|
|
std::to_string(ptr->resSerNo) + ptr->chain +
|
|
" does not match expected count"
|
|
<< std::endl;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool sameResidue(const AtomRecord* ptr1, const AtomRecord* ptr2) {
|
|
if ((ptr1->flag | ptr2->flag) & HetAtmFlag) return (false);
|
|
|
|
return ((ptr1->resSerNo == ptr2->resSerNo) &&
|
|
(ptr1->resName.compare(0, 3, ptr2->resName, 0, 3) == 0) &&
|
|
(ptr1->insert == ptr2->insert) && (ptr1->chain == ptr2->chain));
|
|
}
|
|
|
|
static double calculateCompactness(double d_surfaceArea, double d_totalVolume) {
|
|
return d_surfaceArea / cbrt(36.0 * M_PI * d_totalVolume * d_totalVolume);
|
|
};
|
|
|
|
struct State {
|
|
ElemStruct elemA; // Alpha Carbon //
|
|
ElemStruct elemC; // Peptide Carbon Carbon //
|
|
ElemStruct elemN; // Peptide Nitrogen Nitrogen //
|
|
ElemStruct elemO; // Peptide Oxygen Oxygen //
|
|
ElemStruct elemS; // Sulphur //
|
|
ElemStruct elemX; // Sidechain Atom Unknown //
|
|
|
|
AtomList* grid[VOXORDER][VOXORDER][VOXORDER];
|
|
AtomList* freeList;
|
|
|
|
AtomList* neighbours;
|
|
const AtomRecord* recordCache;
|
|
|
|
unsigned long totalDots;
|
|
ElemStruct standardDots;
|
|
double standardArea;
|
|
|
|
double voxX;
|
|
double voxY;
|
|
double voxZ;
|
|
double voxU;
|
|
double voxV;
|
|
double voxW;
|
|
double cenX;
|
|
double cenY;
|
|
double cenZ;
|
|
|
|
void tesselate(const Point3D& p, const Point3D& q, const Point3D& r,
|
|
unsigned int d) {
|
|
Point3D u, v, w;
|
|
|
|
if (d--) {
|
|
u = p + q;
|
|
v = q + r;
|
|
w = r + p;
|
|
u.normalize();
|
|
v.normalize();
|
|
w.normalize();
|
|
|
|
tesselate(u, v, w, d);
|
|
tesselate(p, u, w, d);
|
|
tesselate(u, q, v, d);
|
|
tesselate(w, v, r, d);
|
|
} else {
|
|
double area = triangleArea(p, q, r);
|
|
standardDots.dots[standardDots.count].area = area;
|
|
standardArea += area;
|
|
auto& n = standardDots.dots[standardDots.count++].v;
|
|
|
|
n = p + q + r;
|
|
n.normalize();
|
|
}
|
|
}
|
|
|
|
void generateElemPoints(ElemStruct* elem, double rad, double probeRadius,
|
|
int dotDensity) {
|
|
double x, y, z, p, q, xy;
|
|
unsigned int vert;
|
|
|
|
elem->radius = rad;
|
|
rad = rad + probeRadius;
|
|
elem->radius2 = rad * rad;
|
|
|
|
if (dotDensity) {
|
|
long count = ((4.0 * M_PI) * rad * rad * dotDensity);
|
|
std::vector<DotStruct> dots(count);
|
|
|
|
unsigned int equat = sqrt(M_PI * count);
|
|
if (!(vert = equat >> 1)) vert = 1;
|
|
|
|
unsigned int i = 0;
|
|
for (unsigned int j = 0; (i < count) && (j < vert); j++) {
|
|
p = (M_PI * j) / (double)vert;
|
|
z = cos(p);
|
|
xy = sin(p);
|
|
unsigned int horz = (equat * xy);
|
|
if (!horz) {
|
|
horz = 1;
|
|
}
|
|
|
|
for (unsigned int k = 0; (i < count) && (k < horz); k++) {
|
|
q = (2.0 * M_PI * k) / (double)horz;
|
|
x = xy * sin(q);
|
|
y = xy * cos(q);
|
|
|
|
dots[i].v.x = rad * x;
|
|
dots[i].v.y = rad * y;
|
|
dots[i].v.z = rad * z;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
count = i;
|
|
double area = ((4.0 * M_PI) * elem->radius2) / count;
|
|
for (DotStruct& dot : dots) {
|
|
dot.area = area;
|
|
}
|
|
|
|
elem->count = count;
|
|
} else {
|
|
elem->count = standardDots.count;
|
|
elem->dots = standardDots.dots;
|
|
}
|
|
}
|
|
|
|
void insertAtomList(AtomList** list, const AtomRecord* ptr) {
|
|
while (*list && ((*list)->count == ATOMPOOL)) {
|
|
list = &(*list)->next;
|
|
}
|
|
|
|
if (!(*list)) {
|
|
if (freeList) {
|
|
*list = freeList;
|
|
freeList = (*list)->next;
|
|
(*list)->next = nullptr;
|
|
(*list)->count = 0;
|
|
} else {
|
|
(*list) = (AtomList*)malloc(sizeof(AtomList));
|
|
(*list)->next = nullptr;
|
|
(*list)->count = 0;
|
|
}
|
|
}
|
|
(*list)->ptr[(*list)->count++] = ptr;
|
|
}
|
|
|
|
void freeAtomList(AtomList* ptr) {
|
|
while (ptr) {
|
|
AtomList* next = ptr->next;
|
|
#if 0
|
|
ptr->next = freeList;
|
|
freeList = ptr;
|
|
#else
|
|
free(ptr);
|
|
#endif
|
|
ptr = next;
|
|
}
|
|
}
|
|
|
|
void freeGrid() {
|
|
for (unsigned int x = 0; x < VOXORDER; x++) {
|
|
for (unsigned int y = 0; y < VOXORDER; y++) {
|
|
for (unsigned int z = 0; z < VOXORDER; z++) {
|
|
freeAtomList(grid[x][y][z]);
|
|
grid[x][y][z] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ElemStruct* getAtomElem(std::string atmName, bool typeFlag) {
|
|
const char* name = atmName.c_str();
|
|
|
|
if (typeFlag) { /* Only Recognise Backbone Atoms! */
|
|
if ((name[1] == 'C') && (name[2] == 'A')) {
|
|
return &elemA;
|
|
} else if (name[2] == ' ')
|
|
switch (name[1]) {
|
|
case 'C':
|
|
return &elemC;
|
|
case 'N':
|
|
return &elemN;
|
|
case 'O':
|
|
return &elemO;
|
|
}
|
|
} else { /* RasMol */
|
|
switch (name[1]) {
|
|
case 'C':
|
|
return &elemC;
|
|
case 'N':
|
|
return &elemN;
|
|
case 'O':
|
|
return &elemO;
|
|
case 'S':
|
|
return &elemS;
|
|
}
|
|
}
|
|
return (&elemX);
|
|
}
|
|
|
|
AtomList* findNeighbours(const AtomRecord& atom, double range) {
|
|
double maxRadius = 1.87;
|
|
double maxDist = range + maxRadius;
|
|
|
|
int lx = voxX * (atom.pos.x - maxDist - voxU);
|
|
if (lx < 0) {
|
|
lx = 0;
|
|
}
|
|
int ly = voxY * (atom.pos.y - maxDist - voxV);
|
|
if (ly < 0) {
|
|
ly = 0;
|
|
}
|
|
int lz = voxZ * (atom.pos.z - maxDist - voxW);
|
|
if (lz < 0) {
|
|
lz = 0;
|
|
}
|
|
|
|
int ux = voxX * (atom.pos.x + maxDist - voxU);
|
|
if (ux >= VOXORDER) {
|
|
ux = VOXORDER - 1;
|
|
}
|
|
int uy = voxY * (atom.pos.y + maxDist - voxV);
|
|
if (uy >= VOXORDER) {
|
|
uy = VOXORDER - 1;
|
|
}
|
|
int uz = voxZ * (atom.pos.z + maxDist - voxW);
|
|
if (uz >= VOXORDER) {
|
|
uz = VOXORDER - 1;
|
|
}
|
|
|
|
AtomList* neighbourList = nullptr;
|
|
for (int x = lx; x <= ux; x++) {
|
|
for (int y = ly; y <= uy; y++) {
|
|
for (int z = lz; z <= uz; z++) {
|
|
for (AtomList* list = grid[x][y][z]; list; list = list->next) {
|
|
for (unsigned int i = 0; i < list->count; i++) {
|
|
const AtomRecord* temp = list->ptr[i];
|
|
if (temp != &atom) {
|
|
maxDist = range + temp->radius;
|
|
if (within(atom, temp, maxDist)) {
|
|
insertAtomList(&neighbourList, temp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return neighbourList;
|
|
}
|
|
|
|
bool testPoint(double* vect, double solvrad) {
|
|
if (recordCache) {
|
|
double dist = recordCache->radius + solvrad;
|
|
double dx = recordCache->pos.x - vect[0];
|
|
double dy = recordCache->pos.y - vect[1];
|
|
double dz = recordCache->pos.z - vect[2];
|
|
if ((dx * dx + dy * dy + dz * dz) < (dist * dist)) {
|
|
return false;
|
|
}
|
|
recordCache = nullptr;
|
|
}
|
|
|
|
for (const AtomList* list = neighbours; list; list = list->next) {
|
|
for (unsigned int i = 0; i < list->count; i++) {
|
|
const AtomRecord* ptr = list->ptr[i];
|
|
double dist = ptr->radius + solvrad;
|
|
double dx = ptr->pos.x - vect[0];
|
|
double dy = ptr->pos.y - vect[1];
|
|
double dz = ptr->pos.z - vect[2];
|
|
if ((dx * dx + dy * dy + dz * dz) < (dist * dist)) {
|
|
recordCache = ptr;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
totalDots++;
|
|
return true;
|
|
}
|
|
|
|
void determineCentreOfGravity(std::vector<AtomRecord>& memberAtoms) {
|
|
double cx, cy, cz;
|
|
|
|
cx = cy = cz = 0.0;
|
|
for (const AtomRecord& atom : memberAtoms) {
|
|
cx += atom.pos.x;
|
|
cy += atom.pos.y;
|
|
cz += atom.pos.z;
|
|
}
|
|
|
|
unsigned int atomCount = memberAtoms.size();
|
|
cenX = cx / atomCount;
|
|
cenY = cy / atomCount;
|
|
cenZ = cz / atomCount;
|
|
}
|
|
|
|
void generateStandardDots(int depth) {
|
|
// Vertex co-ordinates of a unit icosahedron
|
|
static const Point3D Vertices[12] = {{0.00000000, -0.85065081, -0.52573111},
|
|
{-0.52573111, 0.00000000, -0.85065081},
|
|
{-0.85065081, -0.52573111, 0.00000000},
|
|
{0.00000000, -0.85065081, 0.52573111},
|
|
{0.52573111, 0.00000000, -0.85065081},
|
|
{-0.85065081, 0.52573111, 0.00000000},
|
|
{0.00000000, 0.85065081, -0.52573111},
|
|
{-0.52573111, 0.00000000, 0.85065081},
|
|
{0.85065081, -0.52573111, 0.00000000},
|
|
{0.00000000, 0.85065081, 0.52573111},
|
|
{0.52573111, 0.00000000, 0.85065081},
|
|
{0.85065081, 0.52573111, 0.00000000}};
|
|
|
|
// Face list of a unit icosahedron
|
|
static const int Faces[20][4] = {
|
|
{0, 1, 2}, {0, 1, 4}, {0, 2, 3}, {0, 3, 8}, {0, 4, 8},
|
|
{1, 2, 5}, {1, 4, 6}, {1, 5, 6}, {2, 3, 7}, {2, 5, 7},
|
|
{4, 8, 11}, {4, 6, 11}, {3, 8, 10}, {3, 7, 10}, {8, 10, 11},
|
|
{5, 7, 9}, {5, 6, 9}, {6, 9, 11}, {7, 9, 10}, {9, 10, 11}};
|
|
|
|
/* Count = 20*(4^Depth); */
|
|
const unsigned long count = (20) * pow(4, depth);
|
|
std::vector<DotStruct> dots(count);
|
|
|
|
standardDots.radius = 1.0;
|
|
standardDots.dots = dots;
|
|
standardDots.count = 0;
|
|
|
|
standardArea = 0.0;
|
|
for (unsigned int i = 0; i < 20; i++)
|
|
tesselate(Vertices[Faces[i][0]], Vertices[Faces[i][1]],
|
|
Vertices[Faces[i][2]], depth);
|
|
}
|
|
|
|
void generateSurfacePoints(int depth, bool typeFlag, double probeRadius,
|
|
int dotDensity) {
|
|
if (!dotDensity) {
|
|
generateStandardDots(depth);
|
|
}
|
|
|
|
if (typeFlag) {
|
|
generateElemPoints(&elemA, 1.87, probeRadius, dotDensity);
|
|
generateElemPoints(&elemC, 1.76, probeRadius, dotDensity);
|
|
generateElemPoints(&elemN, 1.65, probeRadius, dotDensity);
|
|
generateElemPoints(&elemO, 1.4, probeRadius, dotDensity);
|
|
generateElemPoints(&elemX, 1.8, probeRadius, dotDensity);
|
|
} else {
|
|
generateElemPoints(&elemC, 1.87, probeRadius, dotDensity);
|
|
generateElemPoints(&elemN, 1.5, probeRadius, dotDensity);
|
|
generateElemPoints(&elemO, 1.4, probeRadius, dotDensity);
|
|
generateElemPoints(&elemS, 1.84, probeRadius, dotDensity);
|
|
generateElemPoints(&elemX, 1.44, probeRadius, dotDensity);
|
|
}
|
|
}
|
|
|
|
void createVoxelGrid(int mask, std::vector<AtomRecord>& memberAtoms) {
|
|
double minx, miny, minz;
|
|
double maxx, maxy, maxz;
|
|
|
|
minx = miny = minz = std::numeric_limits<double>::infinity();
|
|
maxx = maxy = maxz = -std::numeric_limits<double>::infinity();
|
|
|
|
freeList = nullptr;
|
|
for (unsigned int x = 0; x < VOXORDER; x++) {
|
|
for (unsigned int y = 0; y < VOXORDER; y++) {
|
|
for (unsigned int z = 0; z < VOXORDER; z++) {
|
|
grid[x][y][z] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
// loop over atoms to find min + max x, y + z
|
|
bool init = false;
|
|
for (const AtomRecord& atom : memberAtoms) {
|
|
if (!(atom.flag & mask)) {
|
|
if (init) {
|
|
if (atom.pos.x > maxx) {
|
|
maxx = atom.pos.x;
|
|
} else if (atom.pos.x < minx) {
|
|
minx = atom.pos.x;
|
|
}
|
|
|
|
if (atom.pos.y > maxy) {
|
|
maxy = atom.pos.y;
|
|
} else if (atom.pos.y < miny) {
|
|
miny = atom.pos.y;
|
|
}
|
|
|
|
if (atom.pos.z > maxz) {
|
|
maxz = atom.pos.z;
|
|
} else if (atom.pos.z < minz) {
|
|
minz = atom.pos.z;
|
|
}
|
|
} else {
|
|
maxx = minx = atom.pos.x;
|
|
maxy = miny = atom.pos.y;
|
|
maxz = minz = atom.pos.z;
|
|
init = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!init) return;
|
|
|
|
voxX = VOXORDER / ((maxx - minx) + 0.1);
|
|
voxU = minx;
|
|
voxY = VOXORDER / ((maxy - miny) + 0.1);
|
|
voxV = miny;
|
|
voxZ = VOXORDER / ((maxz - minz) + 0.1);
|
|
voxW = minz;
|
|
|
|
for (AtomRecord& atom : memberAtoms) {
|
|
if (atom.flag & mask) {
|
|
continue;
|
|
}
|
|
|
|
// get grid positions and add to list
|
|
unsigned int x = voxX * (atom.pos.x - voxU);
|
|
unsigned int y = voxY * (atom.pos.y - voxV);
|
|
unsigned int z = voxZ * (atom.pos.z - voxW);
|
|
insertAtomList(&grid[x][y][z], &atom);
|
|
}
|
|
}
|
|
|
|
double surfaceArea(const AtomRecord& atom, const double solvrad,
|
|
const int dotDensity) {
|
|
neighbours = findNeighbours(atom, atom.elem->radius + solvrad + solvrad);
|
|
recordCache = nullptr;
|
|
|
|
double surfacearea = 0.0;
|
|
double vect[3];
|
|
|
|
if (dotDensity) {
|
|
for (const DotStruct& dot : atom.elem->dots) {
|
|
vect[0] = atom.pos.x + dot.v.x;
|
|
vect[1] = atom.pos.y + dot.v.y;
|
|
vect[2] = atom.pos.z + dot.v.z;
|
|
if (testPoint(vect, solvrad)) {
|
|
surfacearea += dot.area;
|
|
}
|
|
}
|
|
} else {
|
|
double factor = atom.elem->radius + solvrad;
|
|
|
|
for (const DotStruct& dot : atom.elem->dots) {
|
|
vect[0] = atom.pos.x + factor * dot.v.x;
|
|
vect[1] = atom.pos.y + factor * dot.v.y;
|
|
vect[2] = atom.pos.z + factor * dot.v.z;
|
|
|
|
if (testPoint(vect, solvrad)) {
|
|
surfacearea += dot.area;
|
|
}
|
|
}
|
|
surfacearea *= ((4.0 * M_PI) * atom.elem->radius2) / standardArea;
|
|
}
|
|
|
|
freeAtomList(neighbours);
|
|
|
|
return surfacearea;
|
|
}
|
|
|
|
double calculateAccessibility(bool isProtein, double probeRadius,
|
|
int dotDensity,
|
|
std::vector<AtomRecord>& memberAtoms) {
|
|
AtomRecord* ptr = nullptr;
|
|
AtomRecord* prev = nullptr;
|
|
unsigned int count = 0;
|
|
double area = 0;
|
|
|
|
totalDots = 0;
|
|
double totalArea = 0.0;
|
|
unsigned int atomCount = memberAtoms.size();
|
|
|
|
bool init = false;
|
|
|
|
if (isProtein) {
|
|
for (unsigned int i = 0; i < atomCount; i++) {
|
|
ptr = &memberAtoms[i];
|
|
|
|
if (ptr->flag & (WaterFlag)) {
|
|
ptr->bFactor = 0.0;
|
|
} else if (!init) {
|
|
if (!(ptr->flag & HetAtmFlag)) {
|
|
init = true;
|
|
prev = ptr;
|
|
area = surfaceArea(*ptr, probeRadius, dotDensity);
|
|
|
|
if (ptr->atmName != " OXT") {
|
|
count = 1;
|
|
} else {
|
|
count = 0;
|
|
}
|
|
ptr->bFactor = (float)area;
|
|
} else {
|
|
ptr->bFactor = (float)0.0;
|
|
}
|
|
} else if (!sameResidue(ptr, prev)) {
|
|
totalArea += area;
|
|
checkResidue(prev, count);
|
|
while (prev != ptr) {
|
|
if (!(prev->flag & (HetAtmFlag))) {
|
|
prev->bFactor = (float)area;
|
|
}
|
|
prev++;
|
|
}
|
|
|
|
if (!(ptr->flag & HetAtmFlag)) {
|
|
area = surfaceArea(*ptr, probeRadius, dotDensity);
|
|
if (ptr->atmName != " OXT") {
|
|
count = 1;
|
|
} else {
|
|
count = 0;
|
|
}
|
|
ptr->bFactor = (float)area;
|
|
prev = ptr;
|
|
|
|
} else {
|
|
ptr->bFactor = (float)0.0;
|
|
init = false;
|
|
}
|
|
} else if (!(ptr->flag & HetAtmFlag)) {
|
|
ptr->bFactor = (float)surfaceArea(*ptr, probeRadius, dotDensity);
|
|
|
|
area += ptr->bFactor;
|
|
if (ptr->atmName.compare(0, 4, " OXT") != 0) {
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (init) {
|
|
totalArea += area;
|
|
checkResidue(prev, count);
|
|
while (prev != ptr) {
|
|
if (!(prev->flag & (HetAtmFlag))) {
|
|
prev->bFactor = (float)area;
|
|
}
|
|
prev++;
|
|
}
|
|
}
|
|
} else {
|
|
for (const AtomRecord& atom : memberAtoms) {
|
|
area = surfaceArea(atom, probeRadius, dotDensity);
|
|
totalArea += area;
|
|
}
|
|
}
|
|
return totalArea;
|
|
};
|
|
|
|
double partialVolume(const AtomRecord& atom, const double solvrad) {
|
|
double rad = atom.elem->radius + solvrad;
|
|
neighbours = findNeighbours(atom, rad + solvrad);
|
|
recordCache = nullptr;
|
|
|
|
unsigned int count = 0;
|
|
|
|
double px, py, pz;
|
|
px = py = pz = 0.0;
|
|
|
|
double vect[3];
|
|
for (const DotStruct& dot : atom.elem->dots) {
|
|
vect[0] = atom.pos.x + rad * dot.v.x;
|
|
vect[1] = atom.pos.y + rad * dot.v.y;
|
|
vect[2] = atom.pos.z + rad * dot.v.z;
|
|
if (testPoint(vect, solvrad)) {
|
|
px += dot.v.x;
|
|
py += dot.v.y;
|
|
pz += dot.v.z;
|
|
count++;
|
|
}
|
|
}
|
|
freeAtomList(neighbours);
|
|
|
|
/* Calculate Volume with Gauss-Ostrogradskii Theorem */
|
|
double partialvol = (atom.pos.x - cenX) * px + (atom.pos.y - cenY) * py +
|
|
(atom.pos.z - cenZ) * pz;
|
|
partialvol = rad * rad * (partialvol + rad * count);
|
|
|
|
return partialvol;
|
|
}
|
|
|
|
double calculateVolume(const double probeRadius,
|
|
const std::vector<AtomRecord>& memberAtoms) {
|
|
double totalvol = 0.0;
|
|
for (const AtomRecord& atom : memberAtoms) {
|
|
totalvol += partialVolume(atom, probeRadius);
|
|
}
|
|
totalvol *= ((4.0 / 3) * M_PI) / standardDots.count;
|
|
|
|
return totalvol;
|
|
};
|
|
};
|
|
|
|
// constructor definition
|
|
DoubleCubicLatticeVolume::DoubleCubicLatticeVolume(const ROMol& mol,
|
|
bool isProtein,
|
|
bool includeLigand,
|
|
double probeRadius,
|
|
int depth, int dotDensity) {
|
|
//! Class for calculation of the Shrake and Rupley surface area and volume
|
|
//! using the Double Cubic Lattice Method.
|
|
//!
|
|
//! Frank Eisenhaber, Philip Lijnzaad, Patrick Argos, Chris Sander and
|
|
//! Michael Scharf, "The Double Cubic Lattice Method: Efficient Approaches
|
|
//! to Numerical Integration of Surface Area and Volume and to Dot Surface
|
|
//! Contouring of Molecular Assemblies", Journal of Computational Chemistry,
|
|
//! Vol. 16, No. 3, pp. 273-284, 1995.
|
|
|
|
/*!
|
|
|
|
\param mol: input molecule or protein
|
|
\param isProtein: flag to calculate burried surface area of a protein ligand
|
|
complex [default=false, free ligand]
|
|
\param includeLigand: flag to trigger
|
|
inclusion of bound ligand in surface area and volume calculations where
|
|
molecule is a protein [default=true]
|
|
\param probeRadius: radius of the
|
|
sphere representing the probe solvent atom
|
|
\param depth: controls the number
|
|
of dots per atom
|
|
\param dotDensity: controls density of dots per atom
|
|
\return class
|
|
object
|
|
*/
|
|
|
|
if (depth > maxDepth) {
|
|
throw std::range_error("Error: supplied depth exceeds maximum");
|
|
}
|
|
|
|
if (dotDensity > maxDensity) {
|
|
throw std::range_error("Error: supplied density exceeds maximum");
|
|
}
|
|
|
|
if (depth < 0) {
|
|
depth = 0;
|
|
}
|
|
|
|
if (probeRadius < 0.0) {
|
|
probeRadius = (isProtein) ? 1.4 : 1.2;
|
|
}
|
|
|
|
// if not protein, includeLigand should always be true
|
|
if (!isProtein){
|
|
includeLigand = true;
|
|
}
|
|
|
|
// default 5120 faces, ligand
|
|
if (isProtein && probeRadius == 1.2 && depth == 4) {
|
|
probeRadius = 1.4;
|
|
depth = 2; // 320 faces, protein
|
|
}
|
|
|
|
std::unique_ptr<ROMol> nmol{MolOps::removeAllHs(mol, false)};
|
|
|
|
State s;
|
|
s.generateSurfacePoints(depth, isProtein, probeRadius, dotDensity);
|
|
|
|
const Conformer& conf = nmol->getConformer();
|
|
|
|
std::vector<AtomRecord> memberAtoms;
|
|
|
|
for (const auto atom : nmol->atoms()) {
|
|
AtomRecord curr_atom(*atom, conf);
|
|
curr_atom.initFlag();
|
|
if (!curr_atom.solventFlag) {
|
|
curr_atom.elem = s.getAtomElem(curr_atom.atmName, isProtein);
|
|
curr_atom.radius = curr_atom.elem->radius;
|
|
memberAtoms.push_back(curr_atom);
|
|
}
|
|
}
|
|
|
|
int mask = includeLigand ? WaterFlag : HetAtmFlag;
|
|
s.determineCentreOfGravity(memberAtoms);
|
|
s.createVoxelGrid(mask, memberAtoms);
|
|
surfaceArea =
|
|
s.calculateAccessibility(isProtein, probeRadius, dotDensity, memberAtoms);
|
|
totalVolume = s.calculateVolume(probeRadius, memberAtoms);
|
|
vdwVolume = s.calculateVolume(0.0, memberAtoms);
|
|
compactness = calculateCompactness(surfaceArea, totalVolume);
|
|
packingDensity = (vdwVolume / totalVolume);
|
|
s.freeGrid();
|
|
}
|
|
|
|
} // namespace Descriptors
|
|
} // namespace RDKit
|