/*******************************************************************************
shapeAlignment.cpp - Shape-it
Copyright 2012 by Silicos-it, a division of Imacosi BVBA
This file is part of Shape-it.
Shape-it is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Shape-it is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with Shape-it. If not, see .
Shape-it is linked against OpenBabel version 2.
OpenBabel is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 2 of the License.
***********************************************************************/
#include
ShapeAlignment::ShapeAlignment(GaussianVolume & gRef, GaussianVolume & gDb)
: _gRef(&gRef)
, _gDb(&gDb)
, _rAtoms(0)
, _rGauss(0)
, _dAtoms(0)
, _dGauss(0)
, _maxSize(0)
, _maxIter(50)
, _matrixMap()
{
// Loop over the single atom volumes of both molecules and make the combinations
_rAtoms = gRef.levels[0];
_dAtoms = gDb.levels[0];
_rGauss = gRef.gaussians.size();
_dGauss = gDb.gaussians.size();
_maxSize = _rGauss * _dGauss + 1;
}
ShapeAlignment::~ShapeAlignment(void)
{
_gRef = NULL;
_gDb = NULL;
// Clear the matrix map
for (MatIter mi = _matrixMap.begin(); mi != _matrixMap.end(); ++mi) {
if (mi->second != NULL) {
delete mi->second;
mi->second = NULL;
}
}
}
AlignmentInfo ShapeAlignment::gradientAscent(SiMath::Vector rotor)
{
// Create a queue to hold the pairs to process
std::queue < std::pair < unsigned int, unsigned int > >processQueue;
// Helper variables
// Overlap matrix is stored as a double error
double *Aij;
double Aq[4];
double Vij(0.0);
double qAq(0.0);
std::vector < unsigned int >*d1(NULL);
std::vector < unsigned int >*d2(NULL);
std::vector < unsigned int >::iterator it1;
// Gradient information
SiMath::Vector overGrad(4, 0.0);
// Hessian
SiMath::Matrix overHessian(4, 4, 0.0);
// Overlap volume
double atomOverlap(0.0);
double pharmOverlap(0.0);
// Solution info
AlignmentInfo res;
double oldVolume(0.0);
unsigned int iterations(0);
unsigned int mapIndex(0);
// unsigned int DBSize = _gDb->pharmacophores.size();
while (iterations < 20) {
// reset volume
atomOverlap = 0.0;
pharmOverlap = 0.0;
iterations++;
// reset gradient
overGrad = 0.0;
// reset hessian
overHessian = 0.0;
double lambda(0.0);
// iterator over the matrix map
MatIter matIter;
// create atom-atom overlaps
for (unsigned int i(0); i < _rAtoms; ++i) {
for (unsigned int j(0); j < _dAtoms; ++j) {
mapIndex = (i * _dGauss) + j;
if ((matIter =
_matrixMap.find(mapIndex)) == _matrixMap.end()) {
Aij =
_updateMatrixMap(_gRef->gaussians[i],
_gDb->gaussians[j]);
// add to map
_matrixMap[mapIndex] = Aij;
} else {
Aij = matIter->second;
}
// rotor product
Aq[0] =
Aij[0] * rotor[0] + Aij[1] * rotor[1] +
Aij[2] * rotor[2] + Aij[3] * rotor[3];
Aq[1] =
Aij[4] * rotor[0] + Aij[5] * rotor[1] +
Aij[6] * rotor[2] + Aij[7] * rotor[3];
Aq[2] =
Aij[8] * rotor[0] + Aij[9] * rotor[1] +
Aij[10] * rotor[2] + Aij[11] * rotor[3];
Aq[3] =
Aij[12] * rotor[0] + Aij[13] * rotor[1] +
Aij[14] * rotor[2] + Aij[15] * rotor[3];
qAq =
rotor[0] * Aq[0] + rotor[1] * Aq[1] +
rotor[2] * Aq[2] + rotor[3] * Aq[3];
// compute overlap volume
Vij = Aij[16] * exp(-qAq);
// check if overlap is sufficient enough, should be more than 0.1 for atom - atom overlap
if (Vij /
(_gRef->gaussians[i].volume +
_gDb->gaussians[j].volume - Vij) < EPS) {
continue;
}
// add to overlap volume
atomOverlap += Vij;
// update gradient -2Vij (Aijq);
double v2 = 2.0 * Vij;
lambda -= v2 * qAq;
overGrad[0] -= v2 * Aq[0];
overGrad[1] -= v2 * Aq[1];
overGrad[2] -= v2 * Aq[2];
overGrad[3] -= v2 * Aq[3];
// overHessian += 2*Vij(2*Aijq'qAij-Aij); (only upper triangular part)
overHessian[0][0] += v2 * (2.0 * Aq[0] * Aq[0] - Aij[0]);
overHessian[0][1] += v2 * (2.0 * Aq[0] * Aq[1] - Aij[1]);
overHessian[0][2] += v2 * (2.0 * Aq[0] * Aq[2] - Aij[2]);
overHessian[0][3] += v2 * (2.0 * Aq[0] * Aq[3] - Aij[3]);
overHessian[1][1] += v2 * (2.0 * Aq[1] * Aq[1] - Aij[5]);
overHessian[1][2] += v2 * (2.0 * Aq[1] * Aq[2] - Aij[6]);
overHessian[1][3] += v2 * (2.0 * Aq[1] * Aq[3] - Aij[7]);
overHessian[2][2] += v2 * (2.0 * Aq[2] * Aq[2] - Aij[10]);
overHessian[2][3] += v2 * (2.0 * Aq[2] * Aq[3] - Aij[11]);
overHessian[3][3] += v2 * (2.0 * Aq[3] * Aq[3] - Aij[15]);
// loop over child nodes and add to queue
d1 = _gRef->childOverlaps[i];
d2 = _gDb->childOverlaps[j];
// first add (i,child(j))
if (d2 != NULL) {
for (it1 = d2->begin(); it1 != d2->end(); ++it1) {
processQueue.push(std::make_pair < unsigned int,
unsigned int >(i, *it1));
}
}
// second add (child(i),j)
if (d1 != NULL) {
for (it1 = d1->begin(); it1 != d1->end(); ++it1) {
processQueue.push(std::make_pair < unsigned int,
unsigned int >(*it1, j));
}
}
}
}
while (!processQueue.empty()) {
// get next element from queue
std::pair < unsigned int, unsigned int >nextPair =
processQueue.front();
processQueue.pop();
unsigned int i = nextPair.first;
unsigned int j = nextPair.second;
// check cache
mapIndex = (i * _dGauss) + j;
if ((matIter = _matrixMap.find(mapIndex)) == _matrixMap.end()) {
Aij =
_updateMatrixMap(_gRef->gaussians[i],
_gDb->gaussians[j]);
_matrixMap[mapIndex] = Aij;
} else {
Aij = matIter->second;
}
// rotor product
Aq[0] =
Aij[0] * rotor[0] + Aij[1] * rotor[1] + Aij[2] * rotor[2] +
Aij[3] * rotor[3];
Aq[1] =
Aij[4] * rotor[0] + Aij[5] * rotor[1] + Aij[6] * rotor[2] +
Aij[7] * rotor[3];
Aq[2] =
Aij[8] * rotor[0] + Aij[9] * rotor[1] +
Aij[10] * rotor[2] + Aij[11] * rotor[3];
Aq[3] =
Aij[12] * rotor[0] + Aij[13] * rotor[1] +
Aij[14] * rotor[2] + Aij[15] * rotor[3];
qAq =
rotor[0] * Aq[0] + rotor[1] * Aq[1] + rotor[2] * Aq[2] +
rotor[3] * Aq[3];
// compute overlap volume
Vij = Aij[16] * exp(-qAq);
// check if overlap is sufficient enough
if (fabs(Vij) /
(_gRef->gaussians[i].volume + _gDb->gaussians[j].volume -
fabs(Vij)) < EPS) {
continue;
}
atomOverlap += Vij;
double v2 = 2.0 * Vij;
lambda -= v2 * qAq;
// update gradient -2Vij (Cij*Aij*q);
overGrad[0] -= v2 * Aq[0];
overGrad[1] -= v2 * Aq[1];
overGrad[2] -= v2 * Aq[2];
overGrad[3] -= v2 * Aq[3];
// hessian 2*Vij(2*AijqqAij-Aij); (only upper triangular part)
overHessian[0][0] += v2 * (2.0 * Aq[0] * Aq[0] - Aij[0]);
overHessian[0][1] += v2 * (2.0 * Aq[0] * Aq[1] - Aij[1]);
overHessian[0][2] += v2 * (2.0 * Aq[0] * Aq[2] - Aij[2]);
overHessian[0][3] += v2 * (2.0 * Aq[0] * Aq[3] - Aij[3]);
overHessian[1][1] += v2 * (2.0 * Aq[1] * Aq[1] - Aij[5]);
overHessian[1][2] += v2 * (2.0 * Aq[1] * Aq[2] - Aij[6]);
overHessian[1][3] += v2 * (2.0 * Aq[1] * Aq[3] - Aij[7]);
overHessian[2][2] += v2 * (2.0 * Aq[2] * Aq[2] - Aij[10]);
overHessian[2][3] += v2 * (2.0 * Aq[2] * Aq[3] - Aij[11]);
overHessian[3][3] += v2 * (2.0 * Aq[3] * Aq[3] - Aij[15]);
// loop over child nodes and add to queue
d1 = _gRef->childOverlaps[i];
d2 = _gDb->childOverlaps[j];
if (d1 != NULL
&& _gRef->gaussians[i].nbr > _gDb->gaussians[j].nbr) {
for (it1 = d1->begin(); it1 != d1->end(); ++it1) {
// add (child(i),j)
processQueue.push(std::make_pair < unsigned int,
unsigned int >(*it1, j));
}
} else {
// first add (i,child(j))
if (d2 != NULL) {
for (it1 = d2->begin(); it1 != d2->end(); ++it1) {
processQueue.push(std::make_pair < unsigned int,
unsigned int >(i, *it1));
}
}
if (d1 != NULL
&& _gDb->gaussians[j].nbr - _gRef->gaussians[i].nbr <
2) {
for (it1 = d1->begin(); it1 != d1->end(); ++it1) {
// add (child(i),j)
processQueue.push(std::make_pair < unsigned int,
unsigned int >(*it1, j));
}
}
}
}
// check if the new volume is better than the previously found one
// if not quit the loop
if (iterations > 6 && atomOverlap < oldVolume + 0.0001) {
break;
}
// store latest volume found
oldVolume = atomOverlap;
// no measurable overlap between two volumes
if (std::isnan(lambda) || std::isnan(oldVolume) || oldVolume == 0) {
break;
}
// update solution
if (oldVolume > res.overlap) {
res.overlap = atomOverlap;
res.rotor = rotor;
if (res.overlap /
(_gRef->overlap + _gDb->overlap - res.overlap) > 0.99) {
break;
}
}
// update the gradient and hessian
overHessian -= lambda;
// fill lower triangular of the hessian matrix
overHessian[1][0] = overHessian[0][1];
overHessian[2][0] = overHessian[0][2];
overHessian[2][1] = overHessian[1][2];
overHessian[3][0] = overHessian[0][3];
overHessian[3][1] = overHessian[1][3];
overHessian[3][2] = overHessian[2][3];
// update gradient to make h
overGrad[0] -= lambda * rotor[0];
overGrad[1] -= lambda * rotor[1];
overGrad[2] -= lambda * rotor[2];
overGrad[3] -= lambda * rotor[3];
// update gradient based on inverse hessian
SiMath::Vector tmp = rowProduct(overHessian, overGrad);
double h =
overGrad[0] * tmp[0] + overGrad[1] * tmp[1] +
overGrad[2] * tmp[2] + overGrad[3] * tmp[3];
// small scaling of the gradient
h = 1 / h * (overGrad[0] * overGrad[0] +
overGrad[1] * overGrad[1] +
overGrad[2] * overGrad[2] +
overGrad[3] * overGrad[3]);
overGrad *= h;
// update rotor based on gradient information
rotor -= overGrad;
// normalise rotor such that it has unit norm
double nr =
sqrt(rotor[0] * rotor[0] + rotor[1] * rotor[1] +
rotor[2] * rotor[2] + rotor[3] * rotor[3]);
rotor[0] /= nr;
rotor[1] /= nr;
rotor[2] /= nr;
rotor[3] /= nr;
} // end of endless while loop
return res;
}
AlignmentInfo ShapeAlignment::simulatedAnnealing(SiMath::Vector rotor)
{
// create a queue to hold the pairs to process
std::queue < std::pair < unsigned int, unsigned int > >processQueue;
// helper variables
// overlap matrix is stored as a double error
double *Aij;
double Aq[4];
// map store store already computed matrices
MatIter matIter;
double Vij(0.0), qAq(0.0);
std::vector < unsigned int >*d1(NULL);
std::vector < unsigned int >*d2(NULL);
std::vector < unsigned int >::iterator it1;
// overlap volume
double atomOverlap(0.0);
double pharmOverlap(0.0);
double dTemperature = 1.1;
// solution info
AlignmentInfo res;
SiMath::Vector oldRotor(rotor);
SiMath::Vector bestRotor(rotor);
double oldVolume(0.0);
double bestVolume(0.0);
unsigned int iterations(0);
unsigned int sameCount(0);
unsigned int mapIndex(0);
// unsigned int DBSize = _gDb->pharmacophores.size();
while (iterations < _maxIter) {
// reset volume
atomOverlap = 0.0;
pharmOverlap = 0.0;
++iterations;
// temperature of the simulated annealing step
double T = sqrt((1.0 + iterations) / dTemperature);
// create atom-atom overlaps
for (unsigned int i = 0; i < _rAtoms; ++i) {
for (unsigned int j = 0; j < _dAtoms; ++j) {
mapIndex = (i * _dGauss) + j;
if ((matIter =
_matrixMap.find(mapIndex)) == _matrixMap.end()) {
Aij =
_updateMatrixMap(_gRef->gaussians[i],
_gDb->gaussians[j]);
_matrixMap[mapIndex] = Aij;
} else {
Aij = matIter->second;
}
// rotor product
Aq[0] =
Aij[0] * rotor[0] + Aij[1] * rotor[1] +
Aij[2] * rotor[2] + Aij[3] * rotor[3];
Aq[1] =
Aij[4] * rotor[0] + Aij[5] * rotor[1] +
Aij[6] * rotor[2] + Aij[7] * rotor[3];
Aq[2] =
Aij[8] * rotor[0] + Aij[9] * rotor[1] +
Aij[10] * rotor[2] + Aij[11] * rotor[3];
Aq[3] =
Aij[12] * rotor[0] + Aij[13] * rotor[1] +
Aij[14] * rotor[2] + Aij[15] * rotor[3];
qAq =
rotor[0] * Aq[0] + rotor[1] * Aq[1] +
rotor[2] * Aq[2] + rotor[3] * Aq[3];
// compute overlap volume
Vij = Aij[16] * exp(-qAq);
// check if overlap is sufficient enough, should be more than 0.1 for atom - atom overlap
if (Vij /
(_gRef->gaussians[i].volume +
_gDb->gaussians[j].volume - Vij) < EPS) {
continue;
}
// add to overlap volume
atomOverlap += Vij;
// loop over child nodes and add to queue
d1 = _gRef->childOverlaps[i];
d2 = _gDb->childOverlaps[j];
// first add (i,child(j))
if (d2 != NULL) {
for (it1 = d2->begin(); it1 != d2->end(); ++it1) {
processQueue.push(std::make_pair < unsigned int,
unsigned int >(i, *it1));
}
}
// second add (child(i),j)
if (d1 != NULL) {
for (it1 = d1->begin(); it1 != d1->end(); ++it1) {
processQueue.push(std::make_pair < unsigned int,
unsigned int >(*it1, j));
}
}
}
}
while (!processQueue.empty()) {
// get next element from queue
std::pair < unsigned int, unsigned int >nextPair =
processQueue.front();
processQueue.pop();
unsigned int i = nextPair.first;
unsigned int j = nextPair.second;
// check cache
mapIndex = (i * _dGauss) + j;
if ((matIter = _matrixMap.find(mapIndex)) == _matrixMap.end()) {
Aij =
_updateMatrixMap(_gRef->gaussians[i],
_gDb->gaussians[j]);
_matrixMap[mapIndex] = Aij;
} else {
Aij = matIter->second;
}
// rotor product
Aq[0] =
Aij[0] * rotor[0] + Aij[1] * rotor[1] + Aij[2] * rotor[2] +
Aij[3] * rotor[3];
Aq[1] =
Aij[4] * rotor[0] + Aij[5] * rotor[1] + Aij[6] * rotor[2] +
Aij[7] * rotor[3];
Aq[2] =
Aij[8] * rotor[0] + Aij[9] * rotor[1] +
Aij[10] * rotor[2] + Aij[11] * rotor[3];
Aq[3] =
Aij[12] * rotor[0] + Aij[13] * rotor[1] +
Aij[14] * rotor[2] + Aij[15] * rotor[3];
qAq =
rotor[0] * Aq[0] + rotor[1] * Aq[1] + rotor[2] * Aq[2] +
rotor[3] * Aq[3];
// compute overlap volume
Vij = Aij[16] * exp(-qAq);
// check if overlap is sufficient enough
if (fabs(Vij) /
(_gRef->gaussians[i].volume + _gDb->gaussians[j].volume -
fabs(Vij)) < EPS) {
continue;
}
// even number of overlap atoms => addition to volume
atomOverlap += Vij;
// loop over child nodes and add to queue
d1 = _gRef->childOverlaps[i];
d2 = _gDb->childOverlaps[j];
if (d1 != NULL
&& _gRef->gaussians[i].nbr > _gDb->gaussians[j].nbr) {
for (it1 = d1->begin(); it1 != d1->end(); ++it1) {
// add (child(i),j)
processQueue.push(std::make_pair < unsigned int,
unsigned int >(*it1, j));
}
} else {
// first add (i,child(j))
if (d2 != NULL) {
for (it1 = d2->begin(); it1 != d2->end(); ++it1) {
processQueue.push(std::make_pair < unsigned int,
unsigned int >(i, *it1));
}
}
if (d1 != NULL
&& _gDb->gaussians[j].nbr - _gRef->gaussians[i].nbr <
2) {
for (it1 = d1->begin(); it1 != d1->end(); ++it1) {
// add (child(i),j)
processQueue.push(std::make_pair < unsigned int,
unsigned int >(*it1, j));
}
}
}
}
// check if the new volume is better than the previously found one
double overlapVol = atomOverlap;
if (overlapVol < oldVolume) {
double D = exp(-sqrt(oldVolume - overlapVol)) / T;
if (SiMath::randD(0, 1) < D) {
oldRotor = rotor;
oldVolume = overlapVol;
sameCount = 0;
} else {
++sameCount;
if (sameCount == 30) {
iterations = _maxIter;
}
}
} else {
// store latest volume found
oldVolume = overlapVol;
oldRotor = rotor;
// update best found so far
bestRotor = rotor;
bestVolume = overlapVol;
sameCount = 0;
// check if it is better than the best solution found so far
if (overlapVol > res.overlap) {
res.overlap = atomOverlap;
res.rotor = rotor;
if ((res.overlap /
(_gRef->overlap + _gDb->overlap - res.overlap)) >
0.99) {
break;
}
}
}
// make random permutation
// double range = 0.05;
double range = 0.1 / T;
rotor[0] = oldRotor[0] + SiMath::randD(-range, range);
rotor[1] = oldRotor[1] + SiMath::randD(-range, range);
rotor[2] = oldRotor[2] + SiMath::randD(-range, range);
rotor[3] = oldRotor[3] + SiMath::randD(-range, range);
// normalise rotor such that it has unit norm
double nr =
sqrt(rotor[0] * rotor[0] + rotor[1] * rotor[1] +
rotor[2] * rotor[2] + rotor[3] * rotor[3]);
rotor[0] /= nr;
rotor[1] /= nr;
rotor[2] /= nr;
rotor[3] /= nr;
} // end of endless while loop
return res;
}
void ShapeAlignment::setMaxIterations(unsigned int i)
{
_maxIter = i;
return;
}
double *ShapeAlignment::_updateMatrixMap(AtomGaussian & a,
AtomGaussian & b)
{
double *A = new double[17];
// variables to store sum and difference of components
double dx = (a.center.x - b.center.x);
double dx2 = dx * dx;
double dy = (a.center.y - b.center.y);
double dy2 = dy * dy;
double dz = (a.center.z - b.center.z);
double dz2 = dz * dz;
double sx = (a.center.x + b.center.x);
double sx2 = sx * sx;
double sy = (a.center.y + b.center.y);
double sy2 = sy * sy;
double sz = (a.center.z + b.center.z);
double sz2 = sz * sz;
// update overlap matrix
double C = a.alpha * b.alpha / (a.alpha + b.alpha);
A[0] = C * (dx2 + dy2 + dz2);
A[1] = C * (dy * sz - dz * sy);
A[2] = C * (dz * sx - dx * sz);
A[3] = C * (dx * sy - dy * sx);
A[4] = A[1];
A[5] = C * (dx2 + sy2 + sz2);
A[6] = C * (dx * dy - sx * sy);
A[7] = C * (dx * dz - sx * sz);
A[8] = A[2];
A[9] = A[6];
A[10] = C * (sx2 + dy2 + sz2);
A[11] = C * (dy * dz - sy * sz);
A[12] = A[3];
A[13] = A[7];
A[14] = A[11];
A[15] = C * (sx2 + sy2 + dz2);
// last elements holds overlap scaling constant
// even number of overlap atoms => addition to volume
// odd number => substraction
if ((a.nbr + b.nbr) % 2 == 0) {
A[16] = a.C * b.C * pow(PI / (a.alpha + b.alpha), 1.5);
} else {
A[16] = -a.C * b.C * pow(PI / (a.alpha + b.alpha), 1.5);
}
return A;
}