From 0d195b8540c364c3f55ce3d6063627b0e853e596 Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Fri, 2 Feb 2024 19:45:00 -0500 Subject: [PATCH 1/8] Add GPL'ed Open Babel forcefield code Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/forcefield/CMakeLists.txt | 13 +++++++++ avogadro/qtplugins/forcefield/forcefield.cpp | 13 ++++++++- avogadro/qtplugins/openbabel/openbabel.cpp | 1 - cmake/FindOpenBabel3.cmake | 28 ++++++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 cmake/FindOpenBabel3.cmake diff --git a/avogadro/qtplugins/forcefield/CMakeLists.txt b/avogadro/qtplugins/forcefield/CMakeLists.txt index 62079d56a2..39a1fbfb15 100644 --- a/avogadro/qtplugins/forcefield/CMakeLists.txt +++ b/avogadro/qtplugins/forcefield/CMakeLists.txt @@ -5,6 +5,15 @@ set(forcefield_srcs scriptenergy.cpp ) +if (BUILD_GPL_PLUGINS) + find_package(OpenBabel3 REQUIRED) + list(APPEND forcefield_srcs + obenergy.cpp + ) + add_definitions(-DBUILD_GPL_PLUGINS) + include_directories(${OpenBabel3_INCLUDE_DIRS} ${OpenBabel3_INCLUDE_DIR}) +endif() + avogadro_plugin(Forcefield "Force field optimization and dynamics" ExtensionPlugin @@ -16,6 +25,10 @@ avogadro_plugin(Forcefield target_link_libraries(Forcefield PRIVATE Avogadro::Calc) +if (BUILD_GPL_PLUGINS) + target_link_libraries(Forcefield PRIVATE OpenBabel3) +endif() + # Bundled forcefield scripts set(forcefields scripts/ani2x.py diff --git a/avogadro/qtplugins/forcefield/forcefield.cpp b/avogadro/qtplugins/forcefield/forcefield.cpp index 285a9e2920..0a69857894 100644 --- a/avogadro/qtplugins/forcefield/forcefield.cpp +++ b/avogadro/qtplugins/forcefield/forcefield.cpp @@ -8,6 +8,10 @@ #include "obmmenergy.h" #include "scriptenergy.h" +#ifdef BUILD_GPL_PLUGINS +#include "obenergy.h" +#endif + #include #include @@ -62,15 +66,22 @@ Forcefield::Forcefield(QObject* parent_) settings.endGroup(); // add the openbabel calculators in case they don't exist +#ifdef BUILD_GPL_PLUGINS + Calc::EnergyManager::registerModel(new OBEnergy("MMFF94")); + Calc::EnergyManager::registerModel(new OBEnergy("UFF")); + Calc::EnergyManager::registerModel(new OBEnergy("GAFF")); +#else Calc::EnergyManager::registerModel(new OBMMEnergy("MMFF94")); Calc::EnergyManager::registerModel(new OBMMEnergy("UFF")); Calc::EnergyManager::registerModel(new OBMMEnergy("GAFF")); +#endif refreshScripts(); QAction* action = new QAction(this); action->setEnabled(true); - action->setText(tr("Optimize")); + action->setText(tr("Optimize Geometry")); + action->setShortcut(QKeySequence("Ctrl+Alt+O")); action->setData(optimizeAction); action->setProperty("menu priority", 920); connect(action, SIGNAL(triggered()), SLOT(optimize())); diff --git a/avogadro/qtplugins/openbabel/openbabel.cpp b/avogadro/qtplugins/openbabel/openbabel.cpp index 6abb19b2cc..68b6eb3893 100644 --- a/avogadro/qtplugins/openbabel/openbabel.cpp +++ b/avogadro/qtplugins/openbabel/openbabel.cpp @@ -45,7 +45,6 @@ OpenBabel::OpenBabel(QObject* p) auto* action = new QAction(this); action->setEnabled(true); action->setText(tr("Optimize Geometry")); - action->setShortcut(QKeySequence("Ctrl+Alt+O")); connect(action, SIGNAL(triggered()), SLOT(onOptimizeGeometry())); m_actions.push_back(action); diff --git a/cmake/FindOpenBabel3.cmake b/cmake/FindOpenBabel3.cmake new file mode 100644 index 0000000000..4be3e24a81 --- /dev/null +++ b/cmake/FindOpenBabel3.cmake @@ -0,0 +1,28 @@ +# Find the OpenBabel3 library +# +# Defines: +# +# OpenBabel3_FOUND - system has OpenBabel +# OpenBabel3_INCLUDE_DIRS - the OpenBabel include directories +# OpenBabel3_LIBRARY - The OpenBabel library +# +find_path(OpenBabel3_INCLUDE_DIR openbabel3) +find_library(OpenBabel3_LIBRARY NAMES openbabel openbabel3) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OpenBabel3 DEFAULT_MSG OpenBabel3_INCLUDE_DIR + OpenBabel3_LIBRARY) + +mark_as_advanced(OpenBabel3_INCLUDE_DIR OpenBabel3_LIBRARY) + +if(OpenBabel3_FOUND) + set(OpenBabel3_INCLUDE_DIRS "${OpenBabel3_INCLUDE_DIR}") + + if(NOT TARGET OpenBabel3) + add_library(OpenBabel3 SHARED IMPORTED GLOBAL) + set_target_properties(OpenBabel3 PROPERTIES + IMPORTED_LOCATION "${OpenBabel3_LIBRARY}" + IMPORTED_IMPLIB "${OpenBabel3_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${OpenBabel3_INCLUDE_DIR}") + endif() +endif() From 5ab05dca18b1ee8ecda5fdd600a54005e881826b Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Fri, 2 Feb 2024 21:52:35 -0500 Subject: [PATCH 2/8] Use OB code if GPL plugins are used. Otherwise scripts & obmm Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/forcefield/forcefield.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/avogadro/qtplugins/forcefield/forcefield.cpp b/avogadro/qtplugins/forcefield/forcefield.cpp index 0a69857894..9dcf0fcbf6 100644 --- a/avogadro/qtplugins/forcefield/forcefield.cpp +++ b/avogadro/qtplugins/forcefield/forcefield.cpp @@ -67,17 +67,21 @@ Forcefield::Forcefield(QObject* parent_) // add the openbabel calculators in case they don't exist #ifdef BUILD_GPL_PLUGINS + // These directly use Open Babel and are fast Calc::EnergyManager::registerModel(new OBEnergy("MMFF94")); Calc::EnergyManager::registerModel(new OBEnergy("UFF")); Calc::EnergyManager::registerModel(new OBEnergy("GAFF")); -#else +#endif + + refreshScripts(); + +#ifndef BUILD_GPL_PLUGINS + // These call obmm and can be slow Calc::EnergyManager::registerModel(new OBMMEnergy("MMFF94")); Calc::EnergyManager::registerModel(new OBMMEnergy("UFF")); Calc::EnergyManager::registerModel(new OBMMEnergy("GAFF")); #endif - refreshScripts(); - QAction* action = new QAction(this); action->setEnabled(true); action->setText(tr("Optimize Geometry")); From d2d2667bc72b29063c03dba44ebe0fc132671c09 Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Fri, 2 Feb 2024 23:02:28 -0500 Subject: [PATCH 3/8] Add obenergy code Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/forcefield/obenergy.cpp | 184 +++++++++++++++++++++ avogadro/qtplugins/forcefield/obenergy.h | 66 ++++++++ 2 files changed, 250 insertions(+) create mode 100644 avogadro/qtplugins/forcefield/obenergy.cpp create mode 100644 avogadro/qtplugins/forcefield/obenergy.h diff --git a/avogadro/qtplugins/forcefield/obenergy.cpp b/avogadro/qtplugins/forcefield/obenergy.cpp new file mode 100644 index 0000000000..f379b7cac6 --- /dev/null +++ b/avogadro/qtplugins/forcefield/obenergy.cpp @@ -0,0 +1,184 @@ +/****************************************************************************** + This source file is part of the Avogadro project. + This source code is released under the 3-Clause BSD License, (see "LICENSE"). + It links to the Open Babel library, which is released under the GNU GPL v2. +******************************************************************************/ + +#include "obenergy.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace OpenBabel; + +namespace Avogadro::QtPlugins { + +class OBEnergy::Private +{ +public: + // OBMol and OBForceField are owned by this class + OBMol* m_obmol = nullptr; + OBForceField* m_forceField = nullptr; + + ~Private() + { + delete m_obmol; + delete m_forceField; + } +}; + +OBEnergy::OBEnergy(const std::string& method) + : m_identifier(method), m_name(method), m_molecule(nullptr) +{ + d = new Private; + // Ensure the plugins are loaded + OBPlugin::LoadAllPlugins(); + + d->m_forceField = static_cast( + OBPlugin::GetPlugin("forcefields", method.c_str())); + + if (method == "UFF") { + m_description = tr("Universal Force Field"); + m_elements.reset(); + for (unsigned int i = 1; i < 102; ++i) + m_elements.set(i); + } else if (method == "GAFF") { + m_description = tr("Generalized Amber Force Field"); + + // H, C, N, O, F, P, S, Cl, Br, and I + m_elements.set(1); + m_elements.set(6); + m_elements.set(7); + m_elements.set(8); + m_elements.set(9); + m_elements.set(15); + m_elements.set(16); + m_elements.set(17); + m_elements.set(35); + m_elements.set(53); + } else if (method == "MMFF94") { + m_description = tr("Merck Molecular Force Field 94"); + m_elements.reset(); + + // H, C, N, O, F, Si, P, S, Cl, Br, and I + m_elements.set(1); + m_elements.set(6); + m_elements.set(7); + m_elements.set(8); + m_elements.set(9); + m_elements.set(14); + m_elements.set(15); + m_elements.set(16); + m_elements.set(17); + m_elements.set(35); + m_elements.set(53); + } +} + +OBEnergy::~OBEnergy() {} + +Calc::EnergyCalculator* OBEnergy::newInstance() const +{ + return new OBEnergy(m_name); +} + +void OBEnergy::setMolecule(Core::Molecule* mol) +{ + m_molecule = mol; + + if (mol == nullptr || mol->atomCount() == 0) { + return; // nothing to do + } + + // set up our internal OBMol + d->m_obmol = new OBMol; + // copy the atoms, bonds, and coordinates + d->m_obmol->BeginModify(); + for (size_t i = 0; i < mol->atomCount(); ++i) { + const Core::Atom& atom = mol->atom(i); + OBAtom* obAtom = d->m_obmol->NewAtom(); + obAtom->SetAtomicNum(atom.atomicNumber()); + auto pos = atom.position3d().cast(); + obAtom->SetVector(pos.x(), pos.y(), pos.z()); + } + for (size_t i = 0; i < mol->bondCount(); ++i) { + const Core::Bond& bond = mol->bond(i); + d->m_obmol->AddBond(bond.atom1().index() + 1, bond.atom2().index() + 1, + bond.order()); + } + d->m_obmol->EndModify(); + + // make sure we can set up the force field + if (d->m_forceField != nullptr) { + d->m_forceField->Setup(*d->m_obmol); + } else { + d->m_forceField = static_cast( + OBPlugin::GetPlugin("forcefields", m_identifier.c_str())); + if (d->m_forceField != nullptr) { + d->m_forceField->Setup(*d->m_obmol); + } + } +} + +Real OBEnergy::value(const Eigen::VectorXd& x) +{ + if (m_molecule == nullptr || m_molecule->atomCount() == 0) + return 0.0; // nothing to do + + // update coordinates in our private OBMol + for (size_t i = 0; i < m_molecule->atomCount(); ++i) { + Eigen::Vector3d pos(x[i * 3], x[i * 3 + 1], x[i * 3 + 2]); + d->m_obmol->GetAtom(i + 1)->SetVector(pos.x(), pos.y(), pos.z()); + } + + double energy = 0.0; + if (d->m_forceField != nullptr) { + d->m_forceField->SetCoordinates(*d->m_obmol); + energy = d->m_forceField->Energy(false); + } + return energy; +} + +void OBEnergy::gradient(const Eigen::VectorXd& x, Eigen::VectorXd& grad) +{ + if (m_molecule == nullptr || m_molecule->atomCount() == 0) + return; + + // update coordinates in our private OBMol + for (size_t i = 0; i < m_molecule->atomCount(); ++i) { + Eigen::Vector3d pos(x[i * 3], x[i * 3 + 1], x[i * 3 + 2]); + d->m_obmol->GetAtom(i + 1)->SetVector(pos.x(), pos.y(), pos.z()); + } + + if (d->m_forceField != nullptr) { + d->m_forceField->SetCoordinates(*d->m_obmol); + + // make sure gradients are calculated + double energy = d->m_forceField->Energy(true); + for (size_t i = 0; i < m_molecule->atomCount(); ++i) { + OBAtom* atom = d->m_obmol->GetAtom(i + 1); + OpenBabel::vector3 obGrad = d->m_forceField->GetGradient(atom); + grad[3 * i] = obGrad.x(); + grad[3 * i + 1] = obGrad.y(); + grad[3 * i + 2] = obGrad.z(); + } + + grad *= -1; // OpenBabel outputs forces, not grads + cleanGradients(grad); + } +} + +} // namespace Avogadro::QtPlugins + +#include "obenergy.moc" diff --git a/avogadro/qtplugins/forcefield/obenergy.h b/avogadro/qtplugins/forcefield/obenergy.h new file mode 100644 index 0000000000..c9a605dce8 --- /dev/null +++ b/avogadro/qtplugins/forcefield/obenergy.h @@ -0,0 +1,66 @@ +/****************************************************************************** + This source file is part of the Avogadro project. + This source code is released under the 3-Clause BSD License, (see "LICENSE"). + It links to the Open Babel library, which is released under the GNU GPL v2. +******************************************************************************/ + +#ifndef AVOGADRO_QTPLUGINS_OBENERGY_H +#define AVOGADRO_QTPLUGINS_OBENERGY_H + +#include + +#include + +#include +#include + +namespace Avogadro { + +namespace QtPlugins { + +class OBEnergy : public Avogadro::Calc::EnergyCalculator +{ + Q_DECLARE_TR_FUNCTIONS(OBEnergy) + +public: + OBEnergy(const std::string& method = ""); + ~OBEnergy() override; + + std::string method() const { return m_identifier; } + void setupProcess(); + + Calc::EnergyCalculator* newInstance() const override; + + std::string identifier() const override { return m_identifier; } + std::string name() const override { return m_name; } + std::string description() const override + { + return m_description.toStdString(); + } + + Core::Molecule::ElementMask elements() const override { return (m_elements); } + + // This will check if the molecule is valid for this script + // and then start the external process + void setMolecule(Core::Molecule* mol) override; + // energy + Real value(const Eigen::VectorXd& x) override; + // gradient (which may be unsupported and fall back to numeric) + void gradient(const Eigen::VectorXd& x, Eigen::VectorXd& grad) override; + +private: + class Private; + + Core::Molecule* m_molecule; + Private* d; + + Core::Molecule::ElementMask m_elements; + std::string m_identifier; + std::string m_name; + QString m_description; +}; + +} // namespace QtPlugins +} // namespace Avogadro + +#endif // AVOGADRO_QTPLUGINS_OBENERGY_H From 360eff0cac66fb68f8161b92406d2c4cfcb9547c Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Fri, 2 Feb 2024 23:33:22 -0500 Subject: [PATCH 4/8] Small tweak Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/forcefield/obenergy.cpp | 3 ++- cmake/FindOpenBabel3.cmake | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/avogadro/qtplugins/forcefield/obenergy.cpp b/avogadro/qtplugins/forcefield/obenergy.cpp index f379b7cac6..de28ede83e 100644 --- a/avogadro/qtplugins/forcefield/obenergy.cpp +++ b/avogadro/qtplugins/forcefield/obenergy.cpp @@ -8,8 +8,9 @@ #include -#include #include + +#include #include #include #include diff --git a/cmake/FindOpenBabel3.cmake b/cmake/FindOpenBabel3.cmake index 4be3e24a81..e3289b5e9e 100644 --- a/cmake/FindOpenBabel3.cmake +++ b/cmake/FindOpenBabel3.cmake @@ -6,7 +6,7 @@ # OpenBabel3_INCLUDE_DIRS - the OpenBabel include directories # OpenBabel3_LIBRARY - The OpenBabel library # -find_path(OpenBabel3_INCLUDE_DIR openbabel3) +find_path(OpenBabel3_INCLUDE_DIR openbabel3/openbabel/babelconfig.h) find_library(OpenBabel3_LIBRARY NAMES openbabel openbabel3) include(FindPackageHandleStandardArgs) From 42a601180b1ed1ff4daff752bee0b2e015daa83d Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Sat, 3 Feb 2024 00:31:32 -0500 Subject: [PATCH 5/8] Hack the include path Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/forcefield/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/avogadro/qtplugins/forcefield/CMakeLists.txt b/avogadro/qtplugins/forcefield/CMakeLists.txt index 39a1fbfb15..056290ee8e 100644 --- a/avogadro/qtplugins/forcefield/CMakeLists.txt +++ b/avogadro/qtplugins/forcefield/CMakeLists.txt @@ -11,7 +11,8 @@ if (BUILD_GPL_PLUGINS) obenergy.cpp ) add_definitions(-DBUILD_GPL_PLUGINS) - include_directories(${OpenBabel3_INCLUDE_DIRS} ${OpenBabel3_INCLUDE_DIR}) + include_directories(${OpenBabel3_INCLUDE_DIRS} ${OpenBabel3_INCLUDE_DIR} + ${AvogadroLibs_BINARY_DIR}/../prefix/include/openbabel3) endif() avogadro_plugin(Forcefield From 9cc601aafc0a143159be6bbfe11228886a07e903 Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Sat, 3 Feb 2024 08:00:26 -0500 Subject: [PATCH 6/8] Another try for Windows build Signed-off-by: Geoff Hutchison --- cmake/FindOpenBabel3.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/FindOpenBabel3.cmake b/cmake/FindOpenBabel3.cmake index e3289b5e9e..ba5286ddc2 100644 --- a/cmake/FindOpenBabel3.cmake +++ b/cmake/FindOpenBabel3.cmake @@ -7,7 +7,10 @@ # OpenBabel3_LIBRARY - The OpenBabel library # find_path(OpenBabel3_INCLUDE_DIR openbabel3/openbabel/babelconfig.h) -find_library(OpenBabel3_LIBRARY NAMES openbabel openbabel3) +if(OPENBABEL3_INCLUDE_DIR) + set(OPENBABEL3_INCLUDE_DIR ${OPENBABEL3_INCLUDE_DIR}/openbabel3) +endif() +find_library(OpenBabel3_LIBRARY NAMES openbabel openbabel3 openbabel-3) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(OpenBabel3 DEFAULT_MSG OpenBabel3_INCLUDE_DIR From a4f06bddfd0686952f720bf1ebc6100a6114b6a3 Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Sat, 3 Feb 2024 10:38:13 -0500 Subject: [PATCH 7/8] Only add Open Babel if requested and found Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/forcefield/CMakeLists.txt | 30 +++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/avogadro/qtplugins/forcefield/CMakeLists.txt b/avogadro/qtplugins/forcefield/CMakeLists.txt index 056290ee8e..0ffd8de432 100644 --- a/avogadro/qtplugins/forcefield/CMakeLists.txt +++ b/avogadro/qtplugins/forcefield/CMakeLists.txt @@ -6,13 +6,15 @@ set(forcefield_srcs ) if (BUILD_GPL_PLUGINS) - find_package(OpenBabel3 REQUIRED) - list(APPEND forcefield_srcs - obenergy.cpp - ) - add_definitions(-DBUILD_GPL_PLUGINS) - include_directories(${OpenBabel3_INCLUDE_DIRS} ${OpenBabel3_INCLUDE_DIR} - ${AvogadroLibs_BINARY_DIR}/../prefix/include/openbabel3) + find_package(OpenBabel3) + if (OpenBabel3_LIBRARY) + list(APPEND forcefield_srcs + obenergy.cpp + ) + add_definitions(-DBUILD_GPL_PLUGINS) + include_directories(${OpenBabel3_INCLUDE_DIRS} ${OpenBabel3_INCLUDE_DIR} + ${AvogadroLibs_BINARY_DIR}/../prefix/include/openbabel3) + endif() endif() avogadro_plugin(Forcefield @@ -26,20 +28,26 @@ avogadro_plugin(Forcefield target_link_libraries(Forcefield PRIVATE Avogadro::Calc) -if (BUILD_GPL_PLUGINS) +if (BUILD_GPL_PLUGINS AND OpenBabel3_LIBRARY) target_link_libraries(Forcefield PRIVATE OpenBabel3) endif() # Bundled forcefield scripts set(forcefields scripts/ani2x.py - scripts/gaff.py scripts/gfn1.py scripts/gfn2.py scripts/gfnff.py - scripts/mmff94.py - scripts/uff.py ) +if (NOT BUILD_GPL_PLUGINS) + # install the OB / Pybel forcefield scripts + list(APPEND forcefields + scripts/gaff.py + scripts/mmff94.py + scripts/uff.py + ) +endif() + install(PROGRAMS ${forcefields} DESTINATION "${INSTALL_LIBRARY_DIR}/avogadro2/scripts/energy/") From 3ba14d242c4746395e44dfa75ea467138bfa211e Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Sat, 3 Feb 2024 14:20:50 -0500 Subject: [PATCH 8/8] Fix crash Signed-off-by: Geoff Hutchison --- avogadro/calc/energymanager.cpp | 3 +++ avogadro/qtplugins/forcefield/forcefield.cpp | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/avogadro/calc/energymanager.cpp b/avogadro/calc/energymanager.cpp index 53751e45dc..925569d047 100644 --- a/avogadro/calc/energymanager.cpp +++ b/avogadro/calc/energymanager.cpp @@ -113,6 +113,9 @@ std::set EnergyManager::identifiersForMolecule( // check our models for compatibility for (auto m_model : m_models) { + if (m_model == nullptr) + continue; + // we can check easy things first // - is the molecule an ion based on total charge // - is the molecule a radical based on spin multiplicity diff --git a/avogadro/qtplugins/forcefield/forcefield.cpp b/avogadro/qtplugins/forcefield/forcefield.cpp index f4def95687..9dcf0fcbf6 100644 --- a/avogadro/qtplugins/forcefield/forcefield.cpp +++ b/avogadro/qtplugins/forcefield/forcefield.cpp @@ -65,8 +65,6 @@ Forcefield::Forcefield(QObject* parent_) m_gradientTolerance = settings.value("gradientTolerance", 1.0e-4).toDouble(); settings.endGroup(); - refreshScripts(); - // add the openbabel calculators in case they don't exist #ifdef BUILD_GPL_PLUGINS // These directly use Open Babel and are fast