Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions rdkit_extensions/mmff_flattened_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -549,23 +549,29 @@ MMFF::EnergyForceContribsHost constructForcefieldContribs(RDKit::ROMol& mol,
return constructForcefieldContribs(mol, &mmffMolProperties, nonBondedThresh, confId, ignoreInterfragInteractions);
}

std::shared_ptr<RDKit::MMFF::MMFFMolProperties> makeMMFFMolProperties(RDKit::ROMol& mol,
const nvMolKit::MMFFProperties& props) {
auto mmffMolProperties = std::make_shared<RDKit::MMFF::MMFFMolProperties>(mol, props.variant);
PRECONDITION(mmffMolProperties->isValid(), "missing atom types - invalid force-field");
mmffMolProperties->setMMFFVariant(props.variant);
mmffMolProperties->setMMFFDielectricConstant(props.dielectricConstant);
mmffMolProperties->setMMFFDielectricModel(props.dielectricModel);
mmffMolProperties->setMMFFBondTerm(props.bondTerm);
mmffMolProperties->setMMFFAngleTerm(props.angleTerm);
mmffMolProperties->setMMFFStretchBendTerm(props.stretchBendTerm);
mmffMolProperties->setMMFFOopTerm(props.oopTerm);
mmffMolProperties->setMMFFTorsionTerm(props.torsionTerm);
mmffMolProperties->setMMFFVdWTerm(props.vdwTerm);
mmffMolProperties->setMMFFEleTerm(props.eleTerm);
return mmffMolProperties;
}

MMFF::EnergyForceContribsHost constructForcefieldContribs(RDKit::ROMol& mol,
const nvMolKit::MMFFProperties& props,
int confId) {
RDKit::MMFF::MMFFMolProperties mmffMolProperties(mol, props.variant);
PRECONDITION(mmffMolProperties.isValid(), "missing atom types - invalid force-field");
mmffMolProperties.setMMFFVariant(props.variant);
mmffMolProperties.setMMFFDielectricConstant(props.dielectricConstant);
mmffMolProperties.setMMFFDielectricModel(props.dielectricModel);
mmffMolProperties.setMMFFBondTerm(props.bondTerm);
mmffMolProperties.setMMFFAngleTerm(props.angleTerm);
mmffMolProperties.setMMFFStretchBendTerm(props.stretchBendTerm);
mmffMolProperties.setMMFFOopTerm(props.oopTerm);
mmffMolProperties.setMMFFTorsionTerm(props.torsionTerm);
mmffMolProperties.setMMFFVdWTerm(props.vdwTerm);
mmffMolProperties.setMMFFEleTerm(props.eleTerm);
const auto mmffMolProperties = makeMMFFMolProperties(mol, props);
return constructForcefieldContribs(
mol, &mmffMolProperties, props.nonBondedThreshold, confId, props.ignoreInterfragInteractions);
mol, mmffMolProperties.get(), props.nonBondedThreshold, confId, props.ignoreInterfragInteractions);
}

} // namespace MMFF
Expand Down
10 changes: 10 additions & 0 deletions rdkit_extensions/mmff_flattened_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#ifndef NVMOLKIT_MMFF_FLATTENED_BUILDER_H
#define NVMOLKIT_MMFF_FLATTENED_BUILDER_H

#include <memory>
#include <mutex>

#include "src/forcefields/mmff.h"
Expand Down Expand Up @@ -61,6 +62,15 @@ EnergyForceContribsHost constructForcefieldContribs(RDKit::ROMol&
const nvMolKit::MMFFProperties& props,
int confId = -1);

//! Builds the RDKit MMFF atom typing/charges for \c mol.
//! NOTE: RDKit's MMFFMolProperties constructor MUTATES the molecule (Kekulize, setMMFFAromaticity,
//! and a _MMFFSanitized property write). Callers that share a molecule across threads must ensure
//! this runs exactly once per molecule -- see the std::call_once use in the batch minimizers.
//! Returns a shared_ptr so callers need only the forward declaration above: the deleter is bound
//! here, where MMFFMolProperties is complete (its RDKit header is not self-contained).
std::shared_ptr<RDKit::MMFF::MMFFMolProperties> makeMMFFMolProperties(RDKit::ROMol& mol,
const nvMolKit::MMFFProperties& props);

} // namespace nvMolKit::MMFF

#endif // NVMOLKIT_MMFF_FLATTENED_BUILDER_H
125 changes: 86 additions & 39 deletions src/minimizer/mmff_minimize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include <omp.h>

#include <algorithm>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <unordered_map>

Expand All @@ -38,6 +40,31 @@ struct CachedMoleculeData {
EnergyForceContribsHost ffParams;
};

//! Per-molecule MMFF atom typing/charges, shared across batch threads.
//!
//! RDKit's MMFFMolProperties constructor mutates the molecule it types (Kekulize,
//! setMMFFAromaticity, and a _MMFFSanitized property write), and setMMFFAromaticity runs on every
//! construction rather than only the first. Since a molecule's conformers can be spread over
//! several batches, building properties inside the batch loop had concurrent threads mutating the
//! same ROMol -- yielding wrong atom types (and so wrong energies) or a corrupted property dict.
//!
//! Build each molecule's properties exactly once instead. Only the first batch to touch a molecule
//! pays for it, and batches for other molecules never block, so preprocessing stays interleaved
//! with GPU work. The comparatively expensive contribs build stays per-batch and is read-only.
class SharedMolProperties {
public:
explicit SharedMolProperties(size_t numMols) : initFlags_(numMols), properties_(numMols) {}

RDKit::MMFF::MMFFMolProperties* get(size_t molIdx, RDKit::ROMol& mol, const MMFFProperties& props) {
std::call_once(initFlags_[molIdx], [&]() { properties_[molIdx] = makeMMFFMolProperties(mol, props); });
Comment thread
scal444 marked this conversation as resolved.
return properties_[molIdx].get();
}

private:
std::vector<std::once_flag> initFlags_;
std::vector<std::shared_ptr<RDKit::MMFF::MMFFMolProperties>> properties_;
};

std::vector<std::vector<double>> MMFFOptimizeMoleculesConfsBfgs(std::vector<RDKit::ROMol*>& mols,
const int maxIters,
const MMFFProperties& properties,
Expand Down Expand Up @@ -135,24 +162,26 @@ MMFFMinimizeResult MMFFMinimizeMoleculesConfs(std::vector<RDKit::ROMol*>&
collector.converged.setStream(collector.stream);
}
}
SharedMolProperties sharedMolProperties(mols.size());
detail::OpenMPExceptionRegistry exceptionHandler;
#pragma omp parallel for num_threads(ctx.numThreads) schedule(dynamic) default(none) shared(allConformers, \
moleculeEnergies, \
moleculeConverged, \
totalConformers, \
effectiveBatchSize, \
maxIters, \
gradTol, \
properties, \
constraints, \
ctx, \
threadBuffers, \
deviceCollectors, \
deviceOutput, \
useDeviceInput, \
deviceInput, \
deviceInputIndex, \
backend, \
#pragma omp parallel for num_threads(ctx.numThreads) schedule(dynamic) default(none) shared(allConformers, \
moleculeEnergies, \
moleculeConverged, \
totalConformers, \
effectiveBatchSize, \
maxIters, \
gradTol, \
properties, \
constraints, \
ctx, \
threadBuffers, \
deviceCollectors, \
deviceOutput, \
useDeviceInput, \
deviceInput, \
deviceInputIndex, \
backend, \
sharedMolProperties, \
exceptionHandler)
for (size_t batchStart = 0; batchStart < totalConformers; batchStart += effectiveBatchSize) {
try {
Expand Down Expand Up @@ -194,10 +223,18 @@ MMFFMinimizeResult MMFFMinimizeMoleculesConfs(std::vector<RDKit::ROMol*>&

auto it = moleculeCache.find(mol);
if (it == moleculeCache.end()) {
ScopedNvtxRange computeCacheRange("Preprocess single molecule");
CachedMoleculeData cached;
cached.ffParams = constructForcefieldContribs(*mol, properties[confInfo.molIdx]);
it = moleculeCache.insert({mol, std::move(cached)}).first;
ScopedNvtxRange computeCacheRange("Preprocess single molecule");
CachedMoleculeData cached;
const MMFFProperties& molProps = properties[confInfo.molIdx];
// Atom typing is shared and built once per molecule; the contribs build below is
// read-only and stays per-batch.
auto* mmffMolProperties = sharedMolProperties.get(confInfo.molIdx, *mol, molProps);
cached.ffParams = constructForcefieldContribs(*mol,
mmffMolProperties,
molProps.nonBondedThreshold,
/*confId=*/-1,
molProps.ignoreInterfragInteractions);
it = moleculeCache.insert({mol, std::move(cached)}).first;
}

ScopedNvtxRange addToBatchRange("Add conformer to batch data");
Expand Down Expand Up @@ -394,21 +431,23 @@ MMFFMinimizeResult MMFFMinimizeMoleculesConfsFire(
collector.converged.setStream(collector.stream);
}
}
SharedMolProperties sharedMolProperties(mols.size());
detail::OpenMPExceptionRegistry exceptionHandler;
#pragma omp parallel for num_threads(ctx.numThreads) schedule(dynamic) default(none) shared(allConformers, \
moleculeEnergies, \
moleculeConverged, \
totalConformers, \
effectiveBatchSize, \
maxIters, \
fireOptions, \
properties, \
constraints, \
ctx, \
threadBuffers, \
deviceCollectors, \
deviceOutput, \
backend, \
#pragma omp parallel for num_threads(ctx.numThreads) schedule(dynamic) default(none) shared(allConformers, \
moleculeEnergies, \
moleculeConverged, \
totalConformers, \
effectiveBatchSize, \
maxIters, \
fireOptions, \
properties, \
constraints, \
ctx, \
threadBuffers, \
deviceCollectors, \
deviceOutput, \
backend, \
sharedMolProperties, \
exceptionHandler)
for (size_t batchStart = 0; batchStart < totalConformers; batchStart += effectiveBatchSize) {
try {
Expand All @@ -435,10 +474,18 @@ MMFFMinimizeResult MMFFMinimizeMoleculesConfsFire(

auto it = moleculeCache.find(mol);
if (it == moleculeCache.end()) {
ScopedNvtxRange computeCacheRange("Preprocess single molecule");
CachedMoleculeData cached;
cached.ffParams = constructForcefieldContribs(*mol, properties[confInfo.molIdx]);
it = moleculeCache.insert({mol, std::move(cached)}).first;
ScopedNvtxRange computeCacheRange("Preprocess single molecule");
CachedMoleculeData cached;
const MMFFProperties& molProps = properties[confInfo.molIdx];
// Atom typing is shared and built once per molecule; the contribs build below is
// read-only and stays per-batch.
auto* mmffMolProperties = sharedMolProperties.get(confInfo.molIdx, *mol, molProps);
cached.ffParams = constructForcefieldContribs(*mol,
mmffMolProperties,
molProps.nonBondedThreshold,
/*confId=*/-1,
molProps.ignoreInterfragInteractions);
it = moleculeCache.insert({mol, std::move(cached)}).first;
}

ScopedNvtxRange addToBatchRange("Add conformer to batch data");
Expand Down
6 changes: 3 additions & 3 deletions tests/test_fmcs_primitives.cu
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
#include <set>
#include <vector>

#include "fmcs_cuda/fmcs_seed.cuh"
#include "fmcs_cuda/fmcs_seed_queue.cuh"
#include "mcs_common/mcs_cooperative_copy.cuh"
#include "src/mcs/fmcs_cuda/fmcs_seed.cuh"
#include "src/mcs/fmcs_cuda/fmcs_seed_queue.cuh"
#include "src/mcs/mcs_common/mcs_cooperative_copy.cuh"
#include "src/utils/device_vector.h"

namespace {
Expand Down
Loading
Loading