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
8 changes: 5 additions & 3 deletions rdkit_extensions/dist_geom_flattened_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <map>
#include <sstream>

#include "rdkit_extensions/torsion_compat.h"
#include "versions.h"

// No clang-tidy for 1:1 RDKit ports.
Expand Down Expand Up @@ -157,8 +158,9 @@ void addExperimentalTorsionTerms(nvMolKit::DistGeom::Energy3DForceContribsHost&
contribs.experimentalTorsionTerms.idx4.push_back(atomIdx4);

// Add force constants and signs (6 values per torsion)
const auto& signs = etkdgDetails.expTorsionAngles[torsionIdx].first;
const auto& forceConstants = etkdgDetails.expTorsionAngles[torsionIdx].second;
const auto& torsionParameters = detail::getCosineTorsionParameters(etkdgDetails.expTorsionAngles[torsionIdx]);
const auto& signs = torsionParameters.first;
const auto& forceConstants = torsionParameters.second;

// Ensure we have 6 values for each torsion
for (int term = 0; term < TORSION_TERMS_PER_ANGLE; ++term) {
Expand Down Expand Up @@ -541,4 +543,4 @@ nvMolKit::DistGeom::Energy3DForceContribsHost construct3DForceFieldContribs(

// NOLINTEND
} // namespace DistGeom
} // namespace nvMolKit
} // namespace nvMolKit
33 changes: 33 additions & 0 deletions rdkit_extensions/torsion_compat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

#ifndef NVMOLKIT_TORSION_COMPAT_H
#define NVMOLKIT_TORSION_COMPAT_H

#include <stdexcept>
#include <utility>
#include <variant>
#include <vector>

namespace nvMolKit::detail {

using CosineTorsionParameters = std::pair<std::vector<int>, std::vector<double>>;

inline const CosineTorsionParameters& getCosineTorsionParameters(const CosineTorsionParameters& parameters) {
return parameters;
}

// RDKit 2026.09 wraps the existing cosine parameters and new Gaussian
// parameters in a variant. nvMolKit supports the cosine form (ETversion 1/2).
template <typename... Alternatives>
const CosineTorsionParameters& getCosineTorsionParameters(const std::variant<Alternatives...>& parameters) {
const auto* cosine = std::get_if<CosineTorsionParameters>(&parameters);
if (cosine == nullptr) {
throw std::invalid_argument("Gaussian experimental torsion parameters are not supported by nvMolKit");
}
return *cosine;
}

} // namespace nvMolKit::detail

#endif // NVMOLKIT_TORSION_COMPAT_H
42 changes: 42 additions & 0 deletions src/embedder_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,48 @@ constexpr double FOURTH_DIM_MINIMIZE_FOURTH_DIM_WEIGHT =
} // namespace

namespace nvMolKit {
namespace detail {

EmbedArgs cloneEmbedArgs(const EmbedArgs& source) {
#if RDKIT_VERSION_MAJOR > 2026 || (RDKIT_VERSION_MAJOR == 2026 && RDKIT_VERSION_MINOR >= 9)
EmbedArgs result;
result.dim = source.dim;
result.mmat = source.mmat;
result.chiralCenters = source.chiralCenters;
result.tetrahedralCarbons = source.tetrahedralCarbons;
result.doubleBondEnds = source.doubleBondEnds;
result.stereoDoubleBonds = source.stereoDoubleBonds;
result.posVec = source.posVec;
result.stage = source.stage;

const auto& original = source.etkdgDetails;
auto& copied = result.etkdgDetails;
copied.torsionParamKind = original.torsionParamKind;
copied.expTorsionAtoms = original.expTorsionAtoms;
copied.expTorsionAngles = original.expTorsionAngles;
copied.torsionIdx = original.torsionIdx;
copied.improperAtoms = original.improperAtoms;
copied.bonds = original.bonds;
copied.angles = original.angles;
copied.atomNums = original.atomNums;
copied.boundsMatForceScaling = original.boundsMatForceScaling;
copied.constrainedAtoms = original.constrainedAtoms;
copied.distMat = original.distMat;
copied.forceConsts = original.forceConsts;
copied.path14Configs = original.path14Configs;
copied.phiToEnergy = original.phiToEnergy;
copied.phiToGrad = original.phiToGrad;
if (original.internalCoords != nullptr) {
copied.internalCoords = std::make_unique<RDKit::DGeomHelpers::InternalCoordinates>(*original.internalCoords);
}
return result;
#else
return source;
#endif
}

} // namespace detail

namespace DGeomHelpers {
namespace EmbeddingOps {
bool generateInitialCoords(const std::vector<std::unique_ptr<RDGeom::Point>>& positions,
Expand Down
7 changes: 5 additions & 2 deletions src/embedder_utils.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -70,13 +70,16 @@ struct EmbedArgs {
::DistGeom::BoundsMatPtr mmat = nullptr;
::DistGeom::VECT_CHIRALSET chiralCenters;
::DistGeom::VECT_CHIRALSET tetrahedralCarbons;
ForceFields::CrystalFF::CrystalFFDetails etkdgDetails;
ForceFields::CrystalFF::CrystalFFDetails etkdgDetails{};
std::vector<std::tuple<unsigned int, unsigned int, unsigned int>> doubleBondEnds;
std::vector<std::pair<std::vector<unsigned int>, int>> stereoDoubleBonds;
std::vector<double> posVec;
MinimizeStage stage = MinimizeStage::FirstMinimize;
};

//! Copy prepared arguments for one conformer attempt, including owned RDKit state.
EmbedArgs cloneEmbedArgs(const EmbedArgs& source);

} // namespace detail

namespace DGeomHelpers {
Expand Down
2 changes: 1 addition & 1 deletion src/etkdg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ std::optional<DeviceCoordResult> embedMolecules(const std::vector<RDKit::ROMol*>
for (const int molId : molIds) {
// Use the original unique molecules and their prepared eargs
batchMolsWithConfs.push_back(sortedMols[molId]);
batchEargs.push_back(eargs[molId]);
batchEargs.push_back(detail::cloneEmbedArgs(eargs[molId]));
coordinateDimensions.push_back(eargs[molId].chiralCenters.empty() ? 3 : 4);
}

Expand Down
9 changes: 7 additions & 2 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,13 @@ target_link_libraries(smarts_filter PRIVATE molecules molData ${RDKit_LIBS})
add_executable(test_flattened_builder test_flattened_builder.cu)
target_link_libraries(
test_flattened_builder
PRIVATE ${RDKit_LIBS} rdkit_mmff_flattened rdkit_dist_geom_flattened
device_vector test_utils embedder_utils)
PRIVATE ${RDKit_LIBS}
rdkit_mmff_flattened
rdkit_dist_geom_flattened
device_vector
test_utils
embedder_utils
nvmolkit_versions)

add_executable(test_mmff test_mmff.cu)
target_link_libraries(
Expand Down
5 changes: 3 additions & 2 deletions tests/test_distgeom_kernels.cu
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#include "rdkit_extensions/bounds_matrix.h"
#include "rdkit_extensions/dist_geom_flattened_builder.h"
#include "rdkit_extensions/torsion_compat.h"
#include "src/embedder_utils.h"
#include "src/forcefields/dist_geom.h"
#include "src/forcefields/dist_geom_kernels.h"
Expand Down Expand Up @@ -120,8 +121,8 @@ void addExperimentalTorsionTerms(ForceFields::ForceField*
} else {
atomPairs[l * numAtoms + i] = true;
}
torsionContribs
->addContrib(i, j, k, l, etkdgDetails.expTorsionAngles[t].second, etkdgDetails.expTorsionAngles[t].first);
const auto& torsionParameters = nvMolKit::detail::getCosineTorsionParameters(etkdgDetails.expTorsionAngles[t]);
torsionContribs->addContrib(i, j, k, l, torsionParameters.second, torsionParameters.first);
}
if (!torsionContribs->empty()) {
ff->contribs().push_back(std::move(torsionContribs));
Expand Down
55 changes: 55 additions & 0 deletions tests/test_flattened_builder.cu
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,66 @@
#include <gtest/gtest.h>

#include <filesystem>
#include <tuple>

#include "rdkit_extensions/dist_geom_flattened_builder.h"
#include "rdkit_extensions/mmff_flattened_builder.h"
#include "rdkit_extensions/torsion_compat.h"
#include "src/embedder_utils.h"
#include "tests/test_utils.h"
#include "versions.h"

TEST(TorsionCompatibilityTest, LegacyCosineParameters) {
const nvMolKit::detail::CosineTorsionParameters parameters{
{ 1, -1},
{2.5, 3.0}
};
EXPECT_EQ(&nvMolKit::detail::getCosineTorsionParameters(parameters), &parameters);
}

TEST(TorsionCompatibilityTest, VariantCosineParameters) {
using GaussianParameters = std::tuple<std::vector<double>, std::vector<double>, std::vector<double>, double>;
const std::variant<nvMolKit::detail::CosineTorsionParameters, GaussianParameters> parameters =
nvMolKit::detail::CosineTorsionParameters{
{ 1, -1},
{2.5, 3.0}
};
const auto& cosine = nvMolKit::detail::getCosineTorsionParameters(parameters);
EXPECT_EQ(cosine.first, (std::vector<int>{1, -1}));
EXPECT_EQ(cosine.second, (std::vector<double>{2.5, 3.0}));
}

TEST(TorsionCompatibilityTest, RejectsGaussianParameters) {
using GaussianParameters = std::tuple<std::vector<double>, std::vector<double>, std::vector<double>, double>;
const std::variant<nvMolKit::detail::CosineTorsionParameters, GaussianParameters> parameters = GaussianParameters{};
EXPECT_THROW(nvMolKit::detail::getCosineTorsionParameters(parameters), std::invalid_argument);
}

TEST(FlattenedBuilderTest, ClonesPreparedEmbedArgsForConformerAttempts) {
auto mol = std::unique_ptr<RDKit::RWMol>(RDKit::SmilesToMol("CCCC"));
ASSERT_NE(mol, nullptr);
RDKit::MolOps::addHs(*mol);
nvMolKit::detail::EmbedArgs original;
ASSERT_TRUE(nvMolKit::DGeomHelpers::prepareEmbedderArgs(*mol, RDKit::DGeomHelpers::ETKDGv3, original));

auto copied = nvMolKit::detail::cloneEmbedArgs(original);
EXPECT_EQ(copied.mmat, original.mmat);
EXPECT_EQ(copied.etkdgDetails.expTorsionAtoms, original.etkdgDetails.expTorsionAtoms);
EXPECT_EQ(copied.etkdgDetails.expTorsionAngles, original.etkdgDetails.expTorsionAngles);
copied.etkdgDetails.expTorsionAtoms.clear();
EXPECT_FALSE(original.etkdgDetails.expTorsionAtoms.empty());

#if RDKIT_VERSION_MAJOR > 2026 || (RDKIT_VERSION_MAJOR == 2026 && RDKIT_VERSION_MINOR >= 9)
original.etkdgDetails.internalCoords = std::make_unique<RDKit::DGeomHelpers::InternalCoordinates>(3);
original.etkdgDetails.internalCoords->lengths = {1.1, 1.2, 1.3};
auto ownedCopy = nvMolKit::detail::cloneEmbedArgs(original);
ASSERT_NE(ownedCopy.etkdgDetails.internalCoords, nullptr);
EXPECT_NE(ownedCopy.etkdgDetails.internalCoords.get(), original.etkdgDetails.internalCoords.get());
EXPECT_EQ(ownedCopy.etkdgDetails.internalCoords->lengths, original.etkdgDetails.internalCoords->lengths);
ownedCopy.etkdgDetails.internalCoords->lengths[0] = 2.0;
EXPECT_DOUBLE_EQ(original.etkdgDetails.internalCoords->lengths[0], 1.1);
Comment on lines +73 to +81

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Owned-state tests stay skipped

The C++ test job installs RDKit 2025.09.1, and the other build matrices stop at RDKit 2026.3.5. They skip these internalCoords assertions and the new manual copy in cloneEmbedArgs. CI can pass without checking either against the release this PR supports. Add a build-and-test job using RDKit 2026.09 or newer that runs ClonesPreparedEmbedArgsForConformerAttempts.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add CI support for the new version, but this shouldn't block our release, and we need this commit for our release

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s fair — the CI gap does not block the compatibility fix from shipping, especially since you’re planning to add the RDKit 2026.09 job. I’ll treat this as a follow-up validation item rather than a release blocker; the concern is only that the internalCoords ownership path is currently compile-gated out of every existing test matrix.

#endif
}

TEST(FlattenedBuilderTest, NullMolecule) {
RDKit::ROMol mol;
Expand Down
Loading