/******************************************************************************* 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; }