// // Copyright (C) 2024 Greg Landrum and other RDKit contributors // // @@ All Rights Reserved @@ // This file is part of the RDKit. // The contents are covered by the terms of the BSD license // which is included in the file license.txt, found at the root // of the RDKit source tree. // #include "ChiralViolationContribs.h" #include "ChiralSet.h" #include namespace DistGeom { double calcChiralVolume(const unsigned int idx1, const unsigned int idx2, const unsigned int idx3, const unsigned int idx4, const double *pos, const unsigned int dim) { // even if we are minimizing in higher dimension the chiral volume is // calculated using only the first 3 dimensions RDGeom::Point3D v1(pos[idx1 * dim] - pos[idx4 * dim], pos[idx1 * dim + 1] - pos[idx4 * dim + 1], pos[idx1 * dim + 2] - pos[idx4 * dim + 2]); RDGeom::Point3D v2(pos[idx2 * dim] - pos[idx4 * dim], pos[idx2 * dim + 1] - pos[idx4 * dim + 1], pos[idx2 * dim + 2] - pos[idx4 * dim + 2]); RDGeom::Point3D v3(pos[idx3 * dim] - pos[idx4 * dim], pos[idx3 * dim + 1] - pos[idx4 * dim + 1], pos[idx3 * dim + 2] - pos[idx4 * dim + 2]); RDGeom::Point3D v2xv3 = v2.crossProduct(v3); double vol = v1.dotProduct(v2xv3); return vol; } double calcChiralVolume(const unsigned int idx1, const unsigned int idx2, const unsigned int idx3, const unsigned int idx4, const RDGeom::PointPtrVect &pts) { // even if we are minimizing in higher dimension the chiral volume is // calculated using only the first 3 dimensions RDGeom::Point3D v1((*pts[idx1])[0] - (*pts[idx4])[0], (*pts[idx1])[1] - (*pts[idx4])[1], (*pts[idx1])[2] - (*pts[idx4])[2]); RDGeom::Point3D v2((*pts[idx2])[0] - (*pts[idx4])[0], (*pts[idx2])[1] - (*pts[idx4])[1], (*pts[idx2])[2] - (*pts[idx4])[2]); RDGeom::Point3D v3((*pts[idx3])[0] - (*pts[idx4])[0], (*pts[idx3])[1] - (*pts[idx4])[1], (*pts[idx3])[2] - (*pts[idx4])[2]); RDGeom::Point3D v2xv3 = v2.crossProduct(v3); double vol = v1.dotProduct(v2xv3); return vol; } ChiralViolationContribs::ChiralViolationContribs( ForceFields::ForceField *owner) { PRECONDITION(owner, "bad owner"); dp_forceField = owner; } void ChiralViolationContribs::addContrib(const ChiralSet *cset, double weight) { PRECONDITION(dp_forceField, "no owner"); PRECONDITION(cset, "bad chiral set"); URANGE_CHECK(cset->d_idx1, dp_forceField->positions().size()); URANGE_CHECK(cset->d_idx2, dp_forceField->positions().size()); URANGE_CHECK(cset->d_idx3, dp_forceField->positions().size()); URANGE_CHECK(cset->d_idx4, dp_forceField->positions().size()); d_contribs.emplace_back(cset->d_idx1, cset->d_idx2, cset->d_idx3, cset->d_idx4, cset->getUpperVolumeBound(), cset->getLowerVolumeBound(), weight); } double ChiralViolationContribs::getEnergy(double *pos) const { PRECONDITION(dp_forceField, "no owner"); PRECONDITION(pos, "bad vector"); const unsigned int dim = dp_forceField->dimension(); double res = 0.0; for (const auto &c : d_contribs) { double vol = calcChiralVolume(c.idx1, c.idx2, c.idx3, c.idx4, pos, dim); if (vol < c.volLower) { res += c.weight * (vol - c.volLower) * (vol - c.volLower); } else if (vol > c.volUpper) { res += c.weight * (vol - c.volUpper) * (vol - c.volUpper); } } return res; } void ChiralViolationContribs::getGrad(double *pos, double *grad) const { PRECONDITION(dp_forceField, "no owner"); PRECONDITION(pos, "bad vector"); const unsigned int dim = dp_forceField->dimension(); for (const auto &c : d_contribs) { // even if we are minimizing in higher dimension the chiral volume is // calculated using only the first 3 dimensions RDGeom::Point3D v1(pos[c.idx1 * dim] - pos[c.idx4 * dim], pos[c.idx1 * dim + 1] - pos[c.idx4 * dim + 1], pos[c.idx1 * dim + 2] - pos[c.idx4 * dim + 2]); RDGeom::Point3D v2(pos[c.idx2 * dim] - pos[c.idx4 * dim], pos[c.idx2 * dim + 1] - pos[c.idx4 * dim + 1], pos[c.idx2 * dim + 2] - pos[c.idx4 * dim + 2]); RDGeom::Point3D v3(pos[c.idx3 * dim] - pos[c.idx4 * dim], pos[c.idx3 * dim + 1] - pos[c.idx4 * dim + 1], pos[c.idx3 * dim + 2] - pos[c.idx4 * dim + 2]); RDGeom::Point3D v2xv3 = v2.crossProduct(v3); double vol = v1.dotProduct(v2xv3); double preFactor; if (vol < c.volLower) { preFactor = c.weight * (vol - c.volLower); } else if (vol > c.volUpper) { preFactor = c.weight * (vol - c.volUpper); } else { continue; } // now comes the hard part - there are a total of 12 variables involved // 4 x 3 - four points and 3 dimensions // grad[dim * c.idx1] += preFactor * ((v2.y) * (v3.z) - (v3.y) * (v2.z)); grad[dim * c.idx1 + 1] += preFactor * ((v3.x) * (v2.z) - (v2.x) * (v3.z)); grad[dim * c.idx1 + 2] += preFactor * ((v2.x) * (v3.y) - (v3.x) * (v2.y)); grad[dim * c.idx2] += preFactor * ((v3.y) * (v1.z) - (v3.z) * (v1.y)); grad[dim * c.idx2 + 1] += preFactor * ((v3.z) * (v1.x) - (v3.x) * (v1.z)); grad[dim * c.idx2 + 2] += preFactor * ((v3.x) * (v1.y) - (v3.y) * (v1.x)); grad[dim * c.idx3] += preFactor * ((v2.z) * (v1.y) - (v2.y) * (v1.z)); grad[dim * c.idx3 + 1] += preFactor * ((v2.x) * (v1.z) - (v2.z) * (v1.x)); grad[dim * c.idx3 + 2] += preFactor * ((v2.y) * (v1.x) - (v2.x) * (v1.y)); grad[dim * c.idx4] += preFactor * (pos[c.idx1 * dim + 2] * (pos[c.idx2 * dim + 1] - pos[c.idx3 * dim + 1]) + pos[c.idx2 * dim + 2] * (pos[c.idx3 * dim + 1] - pos[c.idx1 * dim + 1]) + pos[c.idx3 * dim + 2] * (pos[c.idx1 * dim + 1] - pos[c.idx2 * dim + 1])); grad[dim * c.idx4 + 1] += preFactor * (pos[c.idx1 * dim] * (pos[c.idx2 * dim + 2] - pos[c.idx3 * dim + 2]) + pos[c.idx2 * dim] * (pos[c.idx3 * dim + 2] - pos[c.idx1 * dim + 2]) + pos[c.idx3 * dim] * (pos[c.idx1 * dim + 2] - pos[c.idx2 * dim + 2])); grad[dim * c.idx4 + 2] += preFactor * (pos[c.idx1 * dim + 1] * (pos[c.idx2 * dim] - pos[c.idx3 * dim]) + pos[c.idx2 * dim + 1] * (pos[c.idx3 * dim] - pos[c.idx1 * dim]) + pos[c.idx3 * dim + 1] * (pos[c.idx1 * dim] - pos[c.idx2 * dim])); } } } // namespace DistGeom