Skip to content

Commit

Permalink
New contribs for DG (rdkit#7711)
Browse files Browse the repository at this point in the history
* add angles and distances

* add Inversions

* add torsiona angle contribs

* use new contribs in test

* use new inversion and torsion contribs in dg

* use new distance contribs in dg

* use new angle constraints in dg

* use new constraints in FF tests

* update docstrings

* remove unused import

* include new contribs

* cleanup includes

* make changes requested by @greglandrum

* use std::move instead of release
  • Loading branch information
nmaeder authored Aug 21, 2024
1 parent 3568879 commit a45d4d9
Show file tree
Hide file tree
Showing 22 changed files with 1,327 additions and 179 deletions.
79 changes: 52 additions & 27 deletions Code/DistGeom/DistGeomUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
#include <Numerics/EigenSolvers/PowerEigenSolver.h>
#include <RDGeneral/utils.h>
#include <ForceField/ForceField.h>
#include <ForceField/UFF/DistanceConstraint.h>
#include <ForceField/UFF/AngleConstraint.h>
#include <ForceField/UFF/Inversion.h>
#include <ForceField/DistanceConstraints.h>
#include <ForceField/AngleConstraints.h>
#include <ForceField/UFF/Inversions.h>
#include <GraphMol/ForceFieldHelpers/CrystalFF/TorsionPreferences.h>
#include <GraphMol/ForceFieldHelpers/CrystalFF/TorsionAngleM6.h>
#include <GraphMol/ForceFieldHelpers/CrystalFF/TorsionAngleContribs.h>
#include <boost/dynamic_bitset.hpp>
#include <ForceField/MMFF/Nonbonded.h>

Expand Down Expand Up @@ -269,6 +269,8 @@ void addImproperTorsionTerms(ForceFields::ForceField *ff,
const std::vector<std::vector<int>> &improperAtoms,
boost::dynamic_bitset<> &isImproperConstrained) {
PRECONDITION(ff, "bad force field");
auto inversionContribs =
std::make_unique<ForceFields::UFF::InversionContribs>(ff);
for (const auto &improperAtom : improperAtoms) {
std::vector<int> n(4);
for (unsigned int i = 0; i < 3; ++i) {
Expand All @@ -293,14 +295,16 @@ void addImproperTorsionTerms(ForceFields::ForceField *ff,
break;
}

auto *contrib = new ForceFields::UFF::InversionContrib(
ff, improperAtom[n[0]], improperAtom[n[1]], improperAtom[n[2]],
inversionContribs->addContrib(
improperAtom[n[0]], improperAtom[n[1]], improperAtom[n[2]],
improperAtom[n[3]], improperAtom[4],
static_cast<bool>(improperAtom[5]), forceScalingFactor);
ff->contribs().emplace_back(contrib);
isImproperConstrained[improperAtom[n[1]]] = 1;
}
}
if (!inversionContribs->empty()) {
ff->contribs().push_back(std::move(inversionContribs));
}
}

//! Add experimental torsion angle contributions to a force field
Expand All @@ -319,6 +323,8 @@ void addExperimentalTorsionTerms(
const ForceFields::CrystalFF::CrystalFFDetails &etkdgDetails,
boost::dynamic_bitset<> &atomPairs, unsigned int numAtoms) {
PRECONDITION(ff, "bad force field");
auto torsionContribs =
std::make_unique<ForceFields::CrystalFF::TorsionAngleContribs>(ff);
for (unsigned int t = 0; t < etkdgDetails.expTorsionAtoms.size(); ++t) {
int i = etkdgDetails.expTorsionAtoms[t][0];
int j = etkdgDetails.expTorsionAtoms[t][1];
Expand All @@ -329,10 +335,12 @@ void addExperimentalTorsionTerms(
} else {
atomPairs[l * numAtoms + i] = 1;
}
auto *contrib = new ForceFields::CrystalFF::TorsionAngleContribM6(
ff, i, j, k, l, etkdgDetails.expTorsionAngles[t].second,
etkdgDetails.expTorsionAngles[t].first);
ff->contribs().emplace_back(contrib);
torsionContribs->addContrib(i, j, k, l,
etkdgDetails.expTorsionAngles[t].second,
etkdgDetails.expTorsionAngles[t].first);
}
if (!torsionContribs->empty()) {
ff->contribs().push_back(std::move(torsionContribs));
}
}

Expand All @@ -355,6 +363,8 @@ void add12Terms(ForceFields::ForceField *ff,
RDGeom::Point3DPtrVect &positions, double forceConstant,
unsigned int numAtoms) {
PRECONDITION(ff, "bad force field");
auto distContribs =
std::make_unique<ForceFields::DistanceConstraintContribs>(ff);
for (const auto &bond : etkdgDetails.bonds) {
unsigned int i = bond.first;
unsigned int j = bond.second;
Expand All @@ -364,9 +374,11 @@ void add12Terms(ForceFields::ForceField *ff,
atomPairs[j * numAtoms + i] = 1;
}
double d = ((*positions[i]) - (*positions[j])).length();
auto *contrib = new ForceFields::UFF::DistanceConstraintContrib(
ff, i, j, d - KNOWN_DIST_TOL, d + KNOWN_DIST_TOL, forceConstant);
ff->contribs().emplace_back(contrib);
distContribs->addContrib(i, j, d - KNOWN_DIST_TOL, d + KNOWN_DIST_TOL,
forceConstant);
}
if (!distContribs->empty()) {
ff->contribs().push_back(std::move(distContribs));
}
}
//! Add 1-3 distance constraints with padding at current positions to force
Expand All @@ -393,6 +405,10 @@ void add13Terms(ForceFields::ForceField *ff,
const boost::dynamic_bitset<> &isImproperConstrained,
bool useBasicKnowledge, unsigned int numAtoms) {
PRECONDITION(ff, "bad force field");
auto distContribs =
std::make_unique<ForceFields::DistanceConstraintContribs>(ff);
auto angleContribs =
std::make_unique<ForceFields::AngleConstraintContribs>(ff);
for (const auto &angle : etkdgDetails.angles) {
unsigned int i = angle[0];
unsigned int j = angle[1];
Expand All @@ -405,16 +421,19 @@ void add13Terms(ForceFields::ForceField *ff,
}
// check for triple bonds
if (useBasicKnowledge && angle[3]) {
auto *contrib = new ForceFields::UFF::AngleConstraintContrib(
ff, i, j, k, 179.0, 180.0, 1);
ff->contribs().emplace_back(contrib);
angleContribs->addContrib(i, j, k, 179.0, 180.0, 1);
} else if (!isImproperConstrained[j]) {
double d = ((*positions[i]) - (*positions[k])).length();
auto *contrib = new ForceFields::UFF::DistanceConstraintContrib(
ff, i, k, d - KNOWN_DIST_TOL, d + KNOWN_DIST_TOL, forceConstant);
ff->contribs().emplace_back(contrib);
distContribs->addContrib(i, k, d - KNOWN_DIST_TOL, d + KNOWN_DIST_TOL,
forceConstant);
}
}
if (!angleContribs->empty()) {
ff->contribs().push_back(std::move(angleContribs));
}
if (!distContribs->empty()) {
ff->contribs().push_back(std::move(distContribs));
}
}

//! Add long distance constraints to bounds matrix borders or constrained atoms
Expand All @@ -441,6 +460,8 @@ void addLongRangeDistanceConstraints(
double knownDistanceForceConstant, const BoundsMatrix &mmat,
unsigned int numAtoms) {
PRECONDITION(ff, "bad force field");
auto distContribs =
std::make_unique<ForceFields::DistanceConstraintContribs>(ff);
double fdist = knownDistanceForceConstant;
for (unsigned int i = 1; i < numAtoms; ++i) {
for (unsigned int j = 0; j < i; ++j) {
Expand All @@ -457,12 +478,13 @@ void addLongRangeDistanceConstraints(
u += KNOWN_DIST_TOL;
fdist = knownDistanceForceConstant;
}
auto *contrib = new ForceFields::UFF::DistanceConstraintContrib(
ff, i, j, l, u, fdist);
ff->contribs().emplace_back(contrib);
distContribs->addContrib(i, j, l, u, fdist);
}
}
}
if (!distContribs->empty()) {
ff->contribs().push_back(std::move(distContribs));
}
}

ForceFields::ForceField *construct3DForceField(
Expand Down Expand Up @@ -565,14 +587,17 @@ ForceFields::ForceField *construct3DImproperForceField(
isImproperConstrained);

// Check that SP Centers have an angle of 180 degrees.
auto angleContribs =
std::make_unique<ForceFields::AngleConstraintContribs>(field);
for (const auto &angle : angles) {
if (angle[3]) {
auto *contrib = new ForceFields::UFF::AngleConstraintContrib(
field, angle[0], angle[1], angle[2], 179.0, 180.0,
oobForceScalingFactor);
field->contribs().emplace_back(contrib);
angleContribs->addContrib(angle[0], angle[1], angle[2], 179.0, 180.0,
oobForceScalingFactor);
}
}
if (!angleContribs->empty()) {
field->contribs().push_back(std::move(angleContribs));
}
return field;
} // construct3DImproperForceField
} // namespace DistGeom
143 changes: 143 additions & 0 deletions Code/ForceField/AngleConstraints.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//
// Copyright (C) 2024 Niels Maeder 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 "AngleConstraints.h"
#include "ForceField.h"
#include <cmath>
#include <RDGeneral/Invariant.h>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

namespace ForceFields {
constexpr double RAD2DEG = 180.0 / M_PI;

AngleConstraintContribs::AngleConstraintContribs(ForceField *owner) {
PRECONDITION(owner, "bad owner");
dp_forceField = owner;
}

void AngleConstraintContribs::addContrib(unsigned int idx1, unsigned int idx2,
unsigned int idx3, double minAngleDeg,
double maxAngleDeg,
double forceConst) {
RANGE_CHECK(0.0, minAngleDeg, 180.0);
RANGE_CHECK(0.0, maxAngleDeg, 180.0);
URANGE_CHECK(idx1, dp_forceField->positions().size());
URANGE_CHECK(idx2, dp_forceField->positions().size());
URANGE_CHECK(idx3, dp_forceField->positions().size());
PRECONDITION(maxAngleDeg >= minAngleDeg,
"minAngleDeg must be <= maxAngleDeg");
d_contribs.emplace_back(idx1, idx2, idx3, minAngleDeg, maxAngleDeg,
forceConst);
}
void AngleConstraintContribs::addContrib(unsigned int idx1, unsigned int idx2,
unsigned int idx3, bool relative,
double minAngleDeg, double maxAngleDeg,
double forceConst) {
const RDGeom::PointPtrVect &pos = dp_forceField->positions();
URANGE_CHECK(idx1, pos.size());
URANGE_CHECK(idx2, pos.size());
URANGE_CHECK(idx3, pos.size());
PRECONDITION(maxAngleDeg >= minAngleDeg,
"minAngleDeg must be <= maxAngleDeg");
if (relative) {
const RDGeom::Point3D &p1 = *((RDGeom::Point3D *)pos[idx1]);
const RDGeom::Point3D &p2 = *((RDGeom::Point3D *)pos[idx2]);
const RDGeom::Point3D &p3 = *((RDGeom::Point3D *)pos[idx3]);
const RDGeom::Point3D r[2] = {p1 - p2, p3 - p2};
const double rLengthSq[2] = {std::max(1.0e-5, r[0].lengthSq()),
std::max(1.0e-5, r[1].lengthSq())};
double cosTheta = r[0].dotProduct(r[1]) / sqrt(rLengthSq[0] * rLengthSq[1]);
cosTheta = std::clamp(cosTheta, -1.0, 1.0);
const double angle = RAD2DEG * acos(cosTheta);
minAngleDeg += angle;
maxAngleDeg += angle;
}
RANGE_CHECK(0.0, minAngleDeg, 180.0);
RANGE_CHECK(0.0, maxAngleDeg, 180.0);
d_contribs.emplace_back(idx1, idx2, idx3, minAngleDeg, maxAngleDeg,
forceConst);
}

double AngleConstraintContribs::computeAngleTerm(
const double &angle, const AngleConstraintContribsParams &contrib) const {
double angleTerm = 0.0;
if (angle < contrib.minAngle) {
angleTerm = angle - contrib.minAngle;
} else if (angle > contrib.maxAngle) {
angleTerm = angle - contrib.maxAngle;
}
return angleTerm;
}

double AngleConstraintContribs::getEnergy(double *pos) const {
PRECONDITION(dp_forceField, "no owner");
PRECONDITION(pos, "bad vector");
double accum = 0.0;
for (const auto &contrib : d_contribs) {
const RDGeom::Point3D p1(pos[3 * contrib.idx1], pos[3 * contrib.idx1 + 1],
pos[3 * contrib.idx1 + 2]);
const RDGeom::Point3D p2(pos[3 * contrib.idx2], pos[3 * contrib.idx2 + 1],
pos[3 * contrib.idx2 + 2]);
const RDGeom::Point3D p3(pos[3 * contrib.idx3], pos[3 * contrib.idx3 + 1],
pos[3 * contrib.idx3 + 2]);
const RDGeom::Point3D r[2] = {p1 - p2, p3 - p2};
const double rLengthSq[2] = {std::max(1.0e-5, r[0].lengthSq()),
std::max(1.0e-5, r[1].lengthSq())};
double cosTheta = r[0].dotProduct(r[1]) / sqrt(rLengthSq[0] * rLengthSq[1]);
cosTheta = std::clamp(cosTheta, -1.0, 1.0);
const double angle = RAD2DEG * acos(cosTheta);
const double angleTerm = computeAngleTerm(angle, contrib);
accum += contrib.forceConstant * angleTerm * angleTerm;
}
return accum;
}

void AngleConstraintContribs::getGrad(double *pos, double *grad) const {
PRECONDITION(dp_forceField, "no owner");
PRECONDITION(pos, "bad vector");
PRECONDITION(grad, "bad vector");
for (const auto &contrib : d_contribs) {
const RDGeom::Point3D p1(pos[3 * contrib.idx1], pos[3 * contrib.idx1 + 1],
pos[3 * contrib.idx1 + 2]);
const RDGeom::Point3D p2(pos[3 * contrib.idx2], pos[3 * contrib.idx2 + 1],
pos[3 * contrib.idx2 + 2]);
const RDGeom::Point3D p3(pos[3 * contrib.idx3], pos[3 * contrib.idx3 + 1],
pos[3 * contrib.idx3 + 2]);
const RDGeom::Point3D r[2] = {p1 - p2, p3 - p2};
const double rLengthSq[2] = {std::max(1.0e-5, r[0].lengthSq()),
std::max(1.0e-5, r[1].lengthSq())};
double cosTheta = r[0].dotProduct(r[1]) / sqrt(rLengthSq[0] * rLengthSq[1]);
cosTheta = std::clamp(cosTheta, -1.0, 1.0);
const double angle = RAD2DEG * acos(cosTheta);
const double angleTerm = computeAngleTerm(angle, contrib);

const double dE_dTheta = 2.0 * RAD2DEG * contrib.forceConstant * angleTerm;

const RDGeom::Point3D rp = r[1].crossProduct(r[0]);
const double prefactor = dE_dTheta / std::max(1.0e-5, rp.length());
const double t[2] = {-prefactor / rLengthSq[0], prefactor / rLengthSq[1]};
RDGeom::Point3D dedp[3];
dedp[0] = r[0].crossProduct(rp) * t[0];
dedp[2] = r[1].crossProduct(rp) * t[1];
dedp[1] = -dedp[0] - dedp[2];
double *g[3] = {&(grad[3 * contrib.idx1]), &(grad[3 * contrib.idx2]),
&(grad[3 * contrib.idx3])};
for (unsigned int i = 0; i < 3; ++i) {
g[i][0] += dedp[i].x;
g[i][1] += dedp[i].y;
g[i][2] += dedp[i].z;
}
}
}

} // namespace ForceFields
Loading

0 comments on commit a45d4d9

Please sign in to comment.