From 52a45ceb9e8d6ab854f24dcf6c590e467bece3f3 Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Thu, 26 Dec 2024 21:51:14 -0500 Subject: [PATCH] Port molecular orbital table from Avogadro v1 (#1888) * Initial port of code from Avogadro v1 * Move energies and occupancies to BasisSet class, add const qualifiers * Allow both beta and alpha symmetry labels * Move Gaussian and Slater concurrent classes to qtgui for orbital table * For now, skip the progress bar Signed-off-by: Geoff Hutchison --- avogadro/core/basisset.h | 112 +++- avogadro/core/gaussianset.cpp | 20 +- avogadro/core/gaussianset.h | 61 +-- avogadro/core/slaterset.cpp | 8 +- avogadro/core/slaterset.h | 2 +- avogadro/io/cjsonformat.cpp | 7 + avogadro/qtgui/CMakeLists.txt | 10 +- .../gaussiansetconcurrent.cpp | 4 +- .../gaussiansetconcurrent.h | 16 +- .../slatersetconcurrent.cpp | 10 +- .../surfaces => qtgui}/slatersetconcurrent.h | 18 +- avogadro/qtplugins/surfaces/CMakeLists.txt | 25 +- avogadro/qtplugins/surfaces/orbitals.cpp | 504 ++++++++++++++++++ avogadro/qtplugins/surfaces/orbitals.h | 172 ++++++ .../qtplugins/surfaces/orbitaltablemodel.cpp | 308 +++++++++++ .../qtplugins/surfaces/orbitaltablemodel.h | 140 +++++ avogadro/qtplugins/surfaces/orbitalwidget.cpp | 235 ++++++++ avogadro/qtplugins/surfaces/orbitalwidget.h | 98 ++++ avogadro/qtplugins/surfaces/orbitalwidget.ui | 138 +++++ avogadro/qtplugins/surfaces/surfaces.cpp | 7 +- avogadro/qtplugins/surfaces/surfaces.h | 12 +- 21 files changed, 1785 insertions(+), 122 deletions(-) rename avogadro/{qtplugins/surfaces => qtgui}/gaussiansetconcurrent.cpp (98%) rename avogadro/{qtplugins/surfaces => qtgui}/gaussiansetconcurrent.h (87%) rename avogadro/{qtplugins/surfaces => qtgui}/slatersetconcurrent.cpp (97%) rename avogadro/{qtplugins/surfaces => qtgui}/slatersetconcurrent.h (84%) create mode 100644 avogadro/qtplugins/surfaces/orbitals.cpp create mode 100644 avogadro/qtplugins/surfaces/orbitals.h create mode 100644 avogadro/qtplugins/surfaces/orbitaltablemodel.cpp create mode 100644 avogadro/qtplugins/surfaces/orbitaltablemodel.h create mode 100644 avogadro/qtplugins/surfaces/orbitalwidget.cpp create mode 100644 avogadro/qtplugins/surfaces/orbitalwidget.h create mode 100644 avogadro/qtplugins/surfaces/orbitalwidget.ui diff --git a/avogadro/core/basisset.h b/avogadro/core/basisset.h index b8cf5985ec..2adff69f81 100644 --- a/avogadro/core/basisset.h +++ b/avogadro/core/basisset.h @@ -103,7 +103,8 @@ class AVOGADROCORE_EXPORT BasisSet /** * @return The number of molecular orbitals in the BasisSet. */ - virtual unsigned int molecularOrbitalCount(ElectronType type = Paired) = 0; + virtual unsigned int molecularOrbitalCount( + ElectronType type = Paired) const = 0; /** * Check if the given MO number is the HOMO or not. @@ -134,6 +135,72 @@ class AVOGADROCORE_EXPORT BasisSet */ virtual bool isValid() = 0; + /** + * @return the orbital symmetry labels (if they exist) for the MOs + */ + std::vector symmetryLabels(ElectronType type = Paired) const + { + if (type == Paired || type == Alpha) + return m_symmetryLabels[0]; + else + return m_symmetryLabels[1]; + } + + /** + * Set the orbital symmetry labels (a1, t2g, etc.) for the molecular + * orbitals + */ + void setSymmetryLabels(const std::vector& labels, + ElectronType type = Paired); + + /** + * @brief Set the molecular orbital energies, expected in Hartrees. + * @param energies The vector containing energies for the MOs of type + * @param type The type of the electrons being set. + */ + void setMolecularOrbitalEnergy(const std::vector& energies, + ElectronType type = Paired); + + /** + * @brief Set the molecular orbital occupancies. + * @param occ The occupancies for the MOs of type. + * @param type The type of the electrons being set. + */ + void setMolecularOrbitalOccupancy(const std::vector& occ, + ElectronType type = Paired); + + std::vector& moEnergy(ElectronType type = Paired) + { + if (type == Paired || type == Alpha) + return m_moEnergy[0]; + else + return m_moEnergy[1]; + } + + std::vector moEnergy(ElectronType type = Paired) const + { + if (type == Paired || type == Alpha) + return m_moEnergy[0]; + else + return m_moEnergy[1]; + } + + std::vector& moOccupancy(ElectronType type = Paired) + { + if (type == Paired || type == Alpha) + return m_moOccupancy[0]; + else + return m_moOccupancy[1]; + } + + std::vector moOccupancy(ElectronType type = Paired) const + { + if (type == Paired || type == Alpha) + return m_moOccupancy[0]; + else + return m_moOccupancy[1]; + } + protected: /** * Total number of electrons, 0 is alpha electrons and 1 is beta electrons. @@ -159,6 +226,22 @@ class AVOGADROCORE_EXPORT BasisSet * The name of the theory used for the calculation. */ std::string m_theoryName; + + /** + * The orbital symmetry labels (if they exist) for the MOs + */ + std::vector m_symmetryLabels[2]; + + /** + * @brief This block stores energies for the molecular orbitals (same + * convention as the molecular orbital coefficients). + */ + std::vector m_moEnergy[2]; + + /** + * @brief The occupancy of the molecular orbitals. + */ + std::vector m_moOccupancy[2]; }; inline void BasisSet::setElectronCount(unsigned int n, ElectronType type) @@ -194,6 +277,33 @@ inline unsigned int BasisSet::electronCount(ElectronType type) const } } +inline void BasisSet::setSymmetryLabels(const std::vector& labels, + ElectronType type) +{ + if (type == Paired || type == Alpha) + m_symmetryLabels[0] = labels; + else + m_symmetryLabels[1] = labels; +} + +inline void BasisSet::setMolecularOrbitalEnergy( + const std::vector& energies, ElectronType type) +{ + if (type == Beta) + m_moEnergy[1] = energies; + else + m_moEnergy[0] = energies; +} + +inline void BasisSet::setMolecularOrbitalOccupancy( + const std::vector& occ, ElectronType type) +{ + if (type == Beta) + m_moOccupancy[1] = occ; + else + m_moOccupancy[0] = occ; +} + } // namespace Avogadro::Core #endif diff --git a/avogadro/core/gaussianset.cpp b/avogadro/core/gaussianset.cpp index 05e66df531..91df13e331 100644 --- a/avogadro/core/gaussianset.cpp +++ b/avogadro/core/gaussianset.cpp @@ -154,24 +154,6 @@ bool GaussianSet::setActiveSetStep(int index) return true; } -void GaussianSet::setMolecularOrbitalEnergy(const vector& energies, - ElectronType type) -{ - if (type == Beta) - m_moEnergy[1] = energies; - else - m_moEnergy[0] = energies; -} - -void GaussianSet::setMolecularOrbitalOccupancy(const vector& occ, - ElectronType type) -{ - if (type == Beta) - m_moOccupancy[1] = occ; - else - m_moOccupancy[0] = occ; -} - void GaussianSet::setMolecularOrbitalNumber(const vector& nums, ElectronType type) { @@ -195,7 +177,7 @@ bool GaussianSet::setSpinDensityMatrix(const MatrixX& m) return true; } -unsigned int GaussianSet::molecularOrbitalCount(ElectronType type) +unsigned int GaussianSet::molecularOrbitalCount(ElectronType type) const { size_t index(0); if (type == Beta) diff --git a/avogadro/core/gaussianset.h b/avogadro/core/gaussianset.h index 634c00daa9..3cd2678f49 100644 --- a/avogadro/core/gaussianset.h +++ b/avogadro/core/gaussianset.h @@ -131,22 +131,6 @@ class AVOGADROCORE_EXPORT GaussianSet : public BasisSet */ bool setActiveSetStep(int index); - /** - * @brief Set the molecular orbital energies, expected in Hartrees. - * @param energies The vector containing energies for the MOs of type - * @param type The type of the electrons being set. - */ - void setMolecularOrbitalEnergy(const std::vector& energies, - ElectronType type = Paired); - - /** - * @brief Set the molecular orbital occupancies. - * @param occ The occupancies for the MOs of type. - * @param type The type of the electrons being set. - */ - void setMolecularOrbitalOccupancy(const std::vector& occ, - ElectronType type = Paired); - /** * @brief This enables support of sparse orbital sets, and provides a mapping * from the index in memory to the actual molecular orbital number. @@ -182,7 +166,7 @@ class AVOGADROCORE_EXPORT GaussianSet : public BasisSet /** * @return The number of molecular orbitals in the GaussianSet. */ - unsigned int molecularOrbitalCount(ElectronType type = Paired) override; + unsigned int molecularOrbitalCount(ElectronType type = Paired) const override; /** * Debug routine, outputs all of the data in the GaussianSet. @@ -260,38 +244,6 @@ class AVOGADROCORE_EXPORT GaussianSet : public BasisSet return m_moMatrix[1]; } - std::vector& moEnergy(ElectronType type = Paired) - { - if (type == Paired || type == Alpha) - return m_moEnergy[0]; - else - return m_moEnergy[1]; - } - - std::vector moEnergy(ElectronType type = Paired) const - { - if (type == Paired || type == Alpha) - return m_moEnergy[0]; - else - return m_moEnergy[1]; - } - - std::vector& moOccupancy(ElectronType type = Paired) - { - if (type == Paired || type == Alpha) - return m_moOccupancy[0]; - else - return m_moOccupancy[1]; - } - - std::vector moOccupancy(ElectronType type = Paired) const - { - if (type == Paired || type == Alpha) - return m_moOccupancy[0]; - else - return m_moOccupancy[1]; - } - std::vector& moNumber(ElectronType type = Paired) { if (type == Paired || type == Alpha) @@ -337,17 +289,6 @@ class AVOGADROCORE_EXPORT GaussianSet : public BasisSet */ std::vector m_moMatrixSet[2]; - /** - * @brief This block stores energies for the molecular orbitals (same - * convention as the molecular orbital coefficients). - */ - std::vector m_moEnergy[2]; - - /** - * @brief The occupancy of the molecular orbitals. - */ - std::vector m_moOccupancy[2]; - /** * @brief This stores the molecular orbital number (when they are sparse). It * is used to lookup the actual index of the molecular orbital data. diff --git a/avogadro/core/slaterset.cpp b/avogadro/core/slaterset.cpp index fc497cf35c..62bc90aef4 100644 --- a/avogadro/core/slaterset.cpp +++ b/avogadro/core/slaterset.cpp @@ -61,14 +61,12 @@ bool SlaterSet::addDensityMatrix(const Eigen::MatrixXd& d) return true; } -unsigned int SlaterSet::molecularOrbitalCount(ElectronType) +unsigned int SlaterSet::molecularOrbitalCount(ElectronType) const { return static_cast(m_overlap.cols()); } -void SlaterSet::outputAll() -{ -} +void SlaterSet::outputAll() {} void SlaterSet::initCalculation() { @@ -133,7 +131,7 @@ void SlaterSet::initCalculation() } } // Convert the exponents into Angstroms - for (double & m_zeta : m_zetas) + for (double& m_zeta : m_zetas) m_zeta = m_zeta / BOHR_TO_ANGSTROM_D; m_initialized = true; diff --git a/avogadro/core/slaterset.h b/avogadro/core/slaterset.h index 619c074eb7..e0a46c883e 100644 --- a/avogadro/core/slaterset.h +++ b/avogadro/core/slaterset.h @@ -116,7 +116,7 @@ class AVOGADROCORE_EXPORT SlaterSet : public BasisSet /** * @return The number of molecular orbitals in the BasisSet. */ - unsigned int molecularOrbitalCount(ElectronType type = Paired) override; + unsigned int molecularOrbitalCount(ElectronType type = Paired) const override; /** * @return True of the basis set is valid, false otherwise. diff --git a/avogadro/io/cjsonformat.cpp b/avogadro/io/cjsonformat.cpp index 3c32446f44..317f15c35e 100644 --- a/avogadro/io/cjsonformat.cpp +++ b/avogadro/io/cjsonformat.cpp @@ -417,6 +417,13 @@ bool CjsonFormat::deserialize(std::istream& file, Molecule& molecule, numArray.push_back(static_cast(number)); basis->setMolecularOrbitalNumber(numArray); } + json symmetryLabels = orbitals["symmetries"]; + if (symmetryLabels.is_array()) { + std::vector symArray; + for (auto& sym : symmetryLabels) + symArray.push_back(sym); + basis->setSymmetryLabels(symArray); + } json moCoefficients = orbitals["moCoefficients"]; json moCoefficientsA = orbitals["alphaCoefficients"]; json moCoefficientsB = orbitals["betaCoefficients"]; diff --git a/avogadro/qtgui/CMakeLists.txt b/avogadro/qtgui/CMakeLists.txt index ff9ea173d4..0865278162 100644 --- a/avogadro/qtgui/CMakeLists.txt +++ b/avogadro/qtgui/CMakeLists.txt @@ -1,7 +1,7 @@ if(QT_VERSION EQUAL 6) - find_package(Qt6 COMPONENTS Widgets REQUIRED) + find_package(Qt6 COMPONENTS Widgets Concurrent REQUIRED) else() - find_package(Qt5 COMPONENTS Widgets REQUIRED) + find_package(Qt5 COMPONENTS Widgets Concurrent REQUIRED) endif() # Provide some simple API to find the plugins, scripts, etc. @@ -40,6 +40,7 @@ avogadro_headers(QtGui extensionplugin.h filebrowsewidget.h fileformatdialog.h + gaussiansetconcurrent.h generichighlighter.h hydrogentools.h insertfragmentdialog.h @@ -62,6 +63,7 @@ avogadro_headers(QtGui sceneplugin.h scenepluginmodel.h scriptloader.h + slatersetconcurrent.h sortfiltertreeproxymodel.h toolplugin.h utilities.h @@ -79,6 +81,7 @@ target_sources(QtGui PRIVATE extensionplugin.cpp filebrowsewidget.cpp fileformatdialog.cpp + gaussiansetconcurrent.cpp generichighlighter.cpp hydrogentools.cpp insertfragmentdialog.cpp @@ -100,6 +103,7 @@ target_sources(QtGui PRIVATE sceneplugin.cpp scenepluginmodel.cpp scriptloader.cpp + slatersetconcurrent.cpp sortfiltertreeproxymodel.cpp toolplugin.cpp utilities.cpp @@ -120,4 +124,4 @@ qt_add_resources(RC_SOURCES ${RCS}) target_sources(QtGui PRIVATE ${RC_SOURCES}) avogadro_add_library(QtGui) -target_link_libraries(QtGui PUBLIC Avogadro::IO Qt::Widgets) +target_link_libraries(QtGui PUBLIC Avogadro::IO Qt::Widgets Qt::Concurrent) diff --git a/avogadro/qtplugins/surfaces/gaussiansetconcurrent.cpp b/avogadro/qtgui/gaussiansetconcurrent.cpp similarity index 98% rename from avogadro/qtplugins/surfaces/gaussiansetconcurrent.cpp rename to avogadro/qtgui/gaussiansetconcurrent.cpp index 099fc7dacd..1c5f3e370a 100644 --- a/avogadro/qtplugins/surfaces/gaussiansetconcurrent.cpp +++ b/avogadro/qtgui/gaussiansetconcurrent.cpp @@ -14,7 +14,7 @@ #include -namespace Avogadro::QtPlugins { +namespace Avogadro::QtGui { using Core::BasisSet; using Core::Cube; @@ -147,4 +147,4 @@ void GaussianSetConcurrent::processSpinDensity(GaussianShell& shell) Vector3 pos = shell.tCube->position(shell.pos); shell.tCube->setValue(shell.pos, shell.tools->calculateSpinDensity(pos)); } -} // namespace Avogadro::QtPlugins +} // namespace Avogadro::QtGui diff --git a/avogadro/qtplugins/surfaces/gaussiansetconcurrent.h b/avogadro/qtgui/gaussiansetconcurrent.h similarity index 87% rename from avogadro/qtplugins/surfaces/gaussiansetconcurrent.h rename to avogadro/qtgui/gaussiansetconcurrent.h index 7e923209c6..dffc12b428 100644 --- a/avogadro/qtplugins/surfaces/gaussiansetconcurrent.h +++ b/avogadro/qtgui/gaussiansetconcurrent.h @@ -3,8 +3,10 @@ This source code is released under the 3-Clause BSD License, (see "LICENSE"). ******************************************************************************/ -#ifndef GAUSSIANSETCONCURRENT_H -#define GAUSSIANSETCONCURRENT_H +#ifndef AVOGADRO_QTGUI_GAUSSIANSETCONCURRENT_H +#define AVOGADRO_QTGUI_GAUSSIANSETCONCURRENT_H + +#include "avogadroqtguiexport.h" #include #include @@ -17,9 +19,9 @@ class Cube; class Molecule; class GaussianSet; class GaussianSetTools; -} +} // namespace Core -namespace QtPlugins { +namespace QtGui { struct GaussianShell; @@ -29,7 +31,7 @@ struct GaussianShell; * @author Marcus D. Hanwell */ -class GaussianSetConcurrent : public QObject +class AVOGADROQTGUI_EXPORT GaussianSetConcurrent : public QObject { Q_OBJECT @@ -74,7 +76,7 @@ private slots: static void processDensity(GaussianShell& shell); static void processSpinDensity(GaussianShell& shell); }; -} -} +} // namespace QtGui +} // namespace Avogadro #endif // GAUSSIANSETCONCURRENT_H diff --git a/avogadro/qtplugins/surfaces/slatersetconcurrent.cpp b/avogadro/qtgui/slatersetconcurrent.cpp similarity index 97% rename from avogadro/qtplugins/surfaces/slatersetconcurrent.cpp rename to avogadro/qtgui/slatersetconcurrent.cpp index dcda4b7fab..baeb3d69ef 100644 --- a/avogadro/qtplugins/surfaces/slatersetconcurrent.cpp +++ b/avogadro/qtgui/slatersetconcurrent.cpp @@ -14,11 +14,11 @@ #include -namespace Avogadro::QtPlugins { +namespace Avogadro::QtGui { +using Core::Cube; using Core::SlaterSet; using Core::SlaterSetTools; -using Core::Cube; struct SlaterShell { @@ -45,8 +45,8 @@ void SlaterSetConcurrent::setMolecule(Core::Molecule* mol) if (!mol) return; m_set = dynamic_cast(mol->basisSet()); - - delete m_tools; + + delete m_tools; m_tools = new SlaterSetTools(mol); } @@ -121,4 +121,4 @@ void SlaterSetConcurrent::processSpinDensity(SlaterShell& shell) Vector3 pos = shell.tCube->position(shell.pos); shell.tCube->setValue(shell.pos, shell.tools->calculateSpinDensity(pos)); } -} +} // namespace Avogadro::QtGui diff --git a/avogadro/qtplugins/surfaces/slatersetconcurrent.h b/avogadro/qtgui/slatersetconcurrent.h similarity index 84% rename from avogadro/qtplugins/surfaces/slatersetconcurrent.h rename to avogadro/qtgui/slatersetconcurrent.h index 3033923b41..9a41af6064 100644 --- a/avogadro/qtplugins/surfaces/slatersetconcurrent.h +++ b/avogadro/qtgui/slatersetconcurrent.h @@ -3,8 +3,10 @@ This source code is released under the 3-Clause BSD License, (see "LICENSE"). ******************************************************************************/ -#ifndef AVOGADRO_QTPLUGINS_SLATERSETCONCURRENT_H -#define AVOGADRO_QTPLUGINS_SLATERSETCONCURRENT_H +#ifndef AVOGADRO_QTGUI_SLATERSETCONCURRENT_H +#define AVOGADRO_QTGUI_SLATERSETCONCURRENT_H + +#include "avogadroqtguiexport.h" #include #include @@ -17,9 +19,9 @@ class Cube; class Molecule; class SlaterSet; class SlaterSetTools; -} +} // namespace Core -namespace QtPlugins { +namespace QtGui { struct SlaterShell; @@ -29,7 +31,7 @@ struct SlaterShell; * @author Marcus D. Hanwell */ -class SlaterSetConcurrent : public QObject +class AVOGADROQTGUI_EXPORT SlaterSetConcurrent : public QObject { Q_OBJECT @@ -73,7 +75,7 @@ private slots: static void processDensity(SlaterShell& shell); static void processSpinDensity(SlaterShell& shell); }; -} -} +} // namespace QtGui +} // namespace Avogadro -#endif // AVOGADRO_QTPLUGINS_SLATERSETCONCURRENT_H +#endif // AVOGADRO_QTGUI_SLATERSETCONCURRENT_H diff --git a/avogadro/qtplugins/surfaces/CMakeLists.txt b/avogadro/qtplugins/surfaces/CMakeLists.txt index 256a65e45d..026b2d4995 100644 --- a/avogadro/qtplugins/surfaces/CMakeLists.txt +++ b/avogadro/qtplugins/surfaces/CMakeLists.txt @@ -3,8 +3,6 @@ if(QT_VERSION EQUAL 6) endif() set(surfaces_srcs - gaussiansetconcurrent.cpp - slatersetconcurrent.cpp surfacedialog.cpp surfaces.cpp ) @@ -28,8 +26,31 @@ target_link_libraries(Surfaces gwavi tinycolormap) +set(orbitals_srcs + orbitaltablemodel.cpp + orbitalwidget.cpp + orbitals.cpp +) + +avogadro_plugin(Orbitals + "Render Molecular Orbitals" + ExtensionPlugin + orbitals.h + Orbitals + "${orbitals_srcs}" + "orbitalwidget.ui" +) + +target_link_libraries(Orbitals + PRIVATE + Avogadro::QtOpenGL + Qt::Concurrent) + if(QT_VERSION EQUAL 6) target_link_libraries(Surfaces PRIVATE Qt6::OpenGL) + target_link_libraries(Orbitals + PRIVATE + Qt6::OpenGL) endif() diff --git a/avogadro/qtplugins/surfaces/orbitals.cpp b/avogadro/qtplugins/surfaces/orbitals.cpp new file mode 100644 index 0000000000..ae0121a144 --- /dev/null +++ b/avogadro/qtplugins/surfaces/orbitals.cpp @@ -0,0 +1,504 @@ +/****************************************************************************** + This source file is part of the Avogadro project. + This source code is released under the 3-Clause BSD License, (see "LICENSE"). +******************************************************************************/ + +#include "orbitals.h" +#include "orbitalwidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Avogadro::QtPlugins { + +const double cubePadding = 2.5; +const int smoothingPasses = 1; + +Orbitals::Orbitals(QObject* p) + : ExtensionPlugin(p), m_molecule(nullptr), m_dialog(nullptr), + m_action(new QAction(this)), m_runningMutex(new QMutex) +{ + m_action->setEnabled(false); + m_action->setText(tr("Molecular Orbitals…")); + connect(m_action, SIGNAL(triggered()), SLOT(openDialog())); +} + +Orbitals::~Orbitals() {} + +QList Orbitals::actions() const +{ + return QList() << m_action; +} + +QStringList Orbitals::menuPath(QAction*) const +{ + QStringList path; + path << tr("&Analysis"); + return path; +} + +void Orbitals::setMolecule(QtGui::Molecule* mol) +{ + if (mol == nullptr) + return; + + if (m_molecule != nullptr) + m_molecule->disconnect(this); + + m_molecule = mol; + // check if it has basis set data + bool hasOrbitals = (m_molecule->basisSet() != nullptr); + + if (hasOrbitals) + m_action->setEnabled(true); + else { + m_action->setEnabled(false); + if (m_dialog) + m_dialog->hide(); + } + + connect(m_molecule, SIGNAL(changed(unsigned int)), + SLOT(moleculeChanged(unsigned int))); + + // Stuff we manage that will not be valid any longer + m_queue.clear(); + m_currentRunningCalculation = -1; + + if (m_basis) { + delete m_basis; + m_basis = nullptr; + } + + loadBasis(); + + if (!m_basis || m_basis->electronCount() == 0) + return; // no electrons, no orbitals + + loadOrbitals(); + precalculateOrbitals(); +} + +void Orbitals::loadBasis() +{ + if (m_molecule != nullptr) { + m_basis = m_molecule->basisSet(); + } +} + +void Orbitals::loadOrbitals() +{ + if (m_basis == nullptr || m_molecule == nullptr) + return; + + if (!m_dialog) { + m_dialog = new OrbitalWidget(qobject_cast(parent()), Qt::Window); + connect(m_dialog, SIGNAL(orbitalSelected(unsigned int)), this, + SLOT(renderOrbital(unsigned int))); + connect(m_dialog, SIGNAL(renderRequested(unsigned int, double)), this, + SLOT(calculateOrbitalFromWidget(unsigned int, double))); + connect(m_dialog, SIGNAL(calculateAll()), this, + SLOT(precalculateOrbitals())); + } + + m_dialog->fillTable(m_basis); + m_dialog->show(); +} + +void Orbitals::moleculeChanged(unsigned int changes) +{ + if (m_molecule == nullptr) + return; + + bool isEnabled = m_action->isEnabled(); + bool hasOrbitals = (m_molecule->basisSet() != nullptr); + + if (isEnabled != hasOrbitals) { + m_action->setEnabled(hasOrbitals); + if (hasOrbitals) + openDialog(); + } +} + +void Orbitals::openDialog() +{ + if (!m_dialog) { + m_dialog = new OrbitalWidget(qobject_cast(parent()), Qt::Window); + connect(m_dialog, SIGNAL(orbitalSelected(unsigned int)), this, + SLOT(renderOrbital(unsigned int))); + connect(m_dialog, SIGNAL(renderRequested(unsigned int, double)), this, + SLOT(calculateOrbitalFromWidget(unsigned int, double))); + connect(m_dialog, SIGNAL(calculateAll()), this, + SLOT(precalculateOrbitals())); + } + + m_dialog->show(); + m_dialog->raise(); +} + +void Orbitals::calculateOrbitalFromWidget(unsigned int orbital, + double resolution) +{ + m_updateMesh = true; + + // check if the orbital is already in the queue + bool found = false; + for (int i = 0; i < m_queue.size(); i++) { + if (m_queue[i].orbital == orbital && m_queue[i].resolution == resolution) { + // change the priority to the highest + m_queue[i].priority = 0; + found = true; + break; + } + } + + if (!found) { + addCalculationToQueue(orbital, resolution, m_dialog->isovalue(), 0); + } + checkQueue(); +} + +void Orbitals::precalculateOrbitals() +{ + if (m_basis == nullptr) + return; + + m_updateMesh = false; + + // Determine HOMO + unsigned int homo = m_basis->homo(); + + // Initialize prioritizer at HOMO's index + int priority = homo; + + // Loop through all MOs, submitting calculations with increasing + // priority until HOMO is reached, submit both HOMO and LUMO at + // priority=1, then start increasing for orbitals above LUMO. + // E.g, + // .... HOMO-2 HOMO-1 HOMO LUMO LUMO+1 LUMO+2 ... << orbitals + // .... 3 2 1 1 2 3 ... << priorities + + // Determine range of precalculated orbitals + int startIndex = + (m_dialog->precalcLimit()) ? homo - (m_dialog->precalcRange() / 2) : 0; + if (startIndex < 0) { + startIndex = 0; + } + int endIndex = (m_dialog->precalcLimit()) + ? homo + (m_dialog->precalcRange() / 2) - 1 + : m_basis->molecularOrbitalCount(); + if (endIndex > m_basis->molecularOrbitalCount() - 1) { + endIndex = m_basis->molecularOrbitalCount() - 1; + } + + for (unsigned int i = startIndex; i <= endIndex; i++) { +#ifndef NDEBUG + qDebug() << " precalculate " << i << " priority " << priority; +#endif + addCalculationToQueue( + i, // orbital + OrbitalWidget::OrbitalQualityToDouble(m_dialog->defaultQuality()), + m_dialog->isovalue(), priority); + + // Update priority. Stays the same when i = homo. + if (i + 1 < homo) + priority--; + else if (i + 1 > homo) + priority++; + } + checkQueue(); +} + +void Orbitals::addCalculationToQueue(unsigned int orbital, double resolution, + double isovalue, unsigned int priority) +{ + // Create new queue entry + calcInfo newCalc; + newCalc.orbital = orbital; + newCalc.resolution = resolution; + newCalc.isovalue = isovalue; + newCalc.priority = priority; + newCalc.state = NotStarted; + + // Add new calculation + m_queue.append(newCalc); + + // Set progress to show 0% + m_dialog->calculationQueued(newCalc.orbital); +} + +void Orbitals::checkQueue() +{ + if (!m_runningMutex->tryLock()) + return; + + // Create a hash: keys=priority, values=indices + + QHash hash; + CalcState state; + + for (int i = 0; i < m_queue.size(); i++) { + state = m_queue.at(i).state; + + // If there is already a running cube, return. + if (state == Running) { + return; + } + + if (state == NotStarted) { + hash.insert(m_queue[i].priority, i); + } + } + + // Do nothing if all calcs are finished. + if (hash.size() == 0) { + m_runningMutex->unlock(); +#ifndef NDEBUG + qDebug() << "Finished queue."; +#endif + return; + } + + QList priorities = hash.keys(); + std::sort(priorities.begin(), priorities.end()); + startCalculation(hash.value(priorities.first())); +} + +void Orbitals::startCalculation(unsigned int queueIndex) +{ + // This will launch calculateMesh when finished. + m_currentRunningCalculation = queueIndex; + + calcInfo* info = &m_queue[m_currentRunningCalculation]; + +#ifndef NDEBUG + qDebug() << info->orbital << " startCalculation() called"; +#endif + + switch (info->state) { + case NotStarted: // Start calculation + calculateCube(); + break; + case Running: // Nothing below should happen... + qWarning() << "startCalculation called on a running calc..."; + break; + case Completed: + qWarning() << "startCalculation called on a completed calc..."; + break; + case Canceled: + qWarning() << "startCalculation called on a canceled calc..."; + break; + } +} + +void Orbitals::calculateCube() +{ + if (m_currentRunningCalculation == -1) + return; + + calcInfo* info = &m_queue[m_currentRunningCalculation]; + + info->state = Running; + + // Check if the cube we want already exists + for (int i = 0; i < m_queue.size(); i++) { + calcInfo* cI = &m_queue[i]; + if (cI->state == Completed && cI->orbital == info->orbital && + cI->resolution == info->resolution) { + info->cube = cI->cube; +#ifndef NDEBUG + qDebug() << "Reusing cube from calculation " << i << ":\n" + << "\tOrbital " << cI->orbital << "\n" + << "\tResolution " << cI->resolution; +#endif + calculatePosMesh(); + return; + } + } + + // Create new cube + Core::Cube* cube = m_molecule->addCube(); + info->cube = cube; + cube->setLimits(*m_molecule, info->resolution, cubePadding); + cube->setName("Molecular Orbital " + std::to_string(info->orbital + 1)); + cube->setCubeType(Core::Cube::Type::MO); + + if (!m_gaussianConcurrent) { + m_gaussianConcurrent = new QtGui::GaussianSetConcurrent(this); + } + m_gaussianConcurrent->setMolecule(m_molecule); + + auto* watcher = &m_gaussianConcurrent->watcher(); + connect(watcher, SIGNAL(finished()), this, SLOT(calculateCubeDone())); + + m_dialog->initializeProgress(info->orbital, watcher->progressMinimum(), + watcher->progressMaximum(), 1, 3); + + connect(watcher, SIGNAL(progressValueChanged(int)), this, + SLOT(updateProgress(int))); + +#ifndef NDEBUG + qDebug() << info->orbital << " Cube calculation started."; +#endif + // TODO: add alpha / beta + m_gaussianConcurrent->calculateMolecularOrbital(cube, info->orbital); +} + +void Orbitals::calculateCubeDone() +{ + auto* watcher = &m_gaussianConcurrent->watcher(); + watcher->disconnect(this); + + if (m_updateMesh) { + m_currentMeshCalculation = m_currentRunningCalculation; + calculatePosMesh(); + } + calculationComplete(); +} + +void Orbitals::calculatePosMesh() +{ + if (m_currentMeshCalculation == -1) + return; + + calcInfo* info = &m_queue[m_currentMeshCalculation]; + + auto posMesh = m_molecule->addMesh(); + auto cube = info->cube; + + if (!m_meshGenerator) { + m_meshGenerator = new QtGui::MeshGenerator; + } + connect(m_meshGenerator, SIGNAL(finished()), SLOT(calculatePosMeshDone())); + m_meshGenerator->initialize(cube, posMesh, m_isoValue, smoothingPasses); + m_meshGenerator->start(); +} + +void Orbitals::calculatePosMeshDone() +{ + disconnect(m_meshGenerator, 0, this, 0); + calculateNegMesh(); +} + +void Orbitals::calculateNegMesh() +{ + if (m_currentMeshCalculation == -1) + return; + + calcInfo* info = &m_queue[m_currentMeshCalculation]; + + auto negMesh = m_molecule->addMesh(); + auto cube = info->cube; + + if (!m_meshGenerator) { + // shouldn't happen, but better to be careful + m_meshGenerator = new QtGui::MeshGenerator; + } + connect(m_meshGenerator, SIGNAL(finished()), SLOT(calculateNegMeshDone())); + // true indicates that we want to reverse the surface + m_meshGenerator->initialize(cube, negMesh, -m_isoValue, smoothingPasses, + true); + m_meshGenerator->start(); +} + +void Orbitals::calculateNegMeshDone() +{ + disconnect(m_meshGenerator, 0, this, 0); + + meshComplete(); + + // ask for a repaint + m_molecule->emitChanged(QtGui::Molecule::Added); +} + +void Orbitals::meshComplete() +{ + if (m_currentMeshCalculation == -1) + return; + + m_currentMeshCalculation = -1; +} + +void Orbitals::calculationComplete() +{ + if (m_currentRunningCalculation == -1) + return; + + calcInfo* info = &m_queue[m_currentRunningCalculation]; + + m_dialog->calculationComplete(info->orbital); + + info->state = Completed; + m_currentRunningCalculation = -1; + m_runningMutex->unlock(); + +#ifndef NDEBUG + qDebug() << info->orbital << " all calculations complete."; +#endif + checkQueue(); +} + +void Orbitals::renderOrbital(unsigned int row) +{ + if (row == 0) + return; + + // table rows are indexed from 1 + // orbitals are indexed from 0 + unsigned int orbital = row - 1; + +#ifndef NDEBUG + qDebug() << "Rendering orbital " << orbital; +#endif + + // Find the most recent calc matching the selected orbital: + calcInfo calc; + int index = -1; + for (int i = 0; i < m_queue.size(); i++) { + calc = m_queue[i]; + if (calc.orbital == orbital && calc.state == Completed) { + index = i; + } + } + + // calculate the meshes + m_molecule->clearMeshes(); + if (index == -1) { + // need to calculate the cube first + calculateOrbitalFromWidget(orbital, OrbitalWidget::OrbitalQualityToDouble( + m_dialog->defaultQuality())); + } else { + // just need to update the meshes + m_currentMeshCalculation = index; + calculatePosMesh(); // will eventually call negMesh too + } + + // add the orbital to the renderer + QStringList displayTypes; + displayTypes << tr("Meshes"); + requestActiveDisplayTypes(displayTypes); +} + +void Orbitals::updateProgress(int current) +{ + if (m_currentRunningCalculation == -1) + return; + + calcInfo* info = &m_queue[m_currentRunningCalculation]; + int orbital = info->orbital; + m_dialog->updateProgress(orbital, current); +} + +} // namespace Avogadro::QtPlugins diff --git a/avogadro/qtplugins/surfaces/orbitals.h b/avogadro/qtplugins/surfaces/orbitals.h new file mode 100644 index 0000000000..017c2ed912 --- /dev/null +++ b/avogadro/qtplugins/surfaces/orbitals.h @@ -0,0 +1,172 @@ +/****************************************************************************** + This source file is part of the Avogadro project. + This source code is released under the 3-Clause BSD License, (see "LICENSE"). +******************************************************************************/ + +#ifndef AVOGADRO_QTPLUGINS_ORBITALS_H +#define AVOGADRO_QTPLUGINS_ORBITALS_H + +#include + +#include + +class QAction; +class QDialog; +class QProgressDialog; + +namespace Avogadro { + +namespace QtGui { +class MeshGenerator; +class GaussianSetConcurrent; +class SlaterSetConcurrent; +} // namespace QtGui + +namespace Core { +class BasisSet; +class Cube; +class Mesh; +} // namespace Core + +namespace QtPlugins { + +/** + * @brief The Orbital plugin shows a window with a list of orbitals and + * .. renders selected orbitals + */ +class OrbitalSettingsDialog; +class OrbitalWidget; + +class Orbitals : public QtGui::ExtensionPlugin +{ + Q_OBJECT + +public: + explicit Orbitals(QObject* parent = nullptr); + ~Orbitals(); + + QString name() const override { return tr("Orbital Window"); } + QString description() const override { return tr("Display orbital lists."); } + + QList actions() const override; + + QStringList menuPath(QAction*) const override; + + void setMolecule(QtGui::Molecule* mol) override; + + enum CalcState + { + NotStarted = 0, + Running, + Completed, + Canceled + }; + + struct calcInfo + { + Core::Mesh* posMesh; + Core::Mesh* negMesh; + Core::Cube* cube; + unsigned int orbital; + double resolution; + double isovalue; + unsigned int priority; + CalcState state; + }; + +public slots: + void moleculeChanged(unsigned int changes); + void openDialog(); + +private slots: + void loadBasis(); + void loadOrbitals(); + + /** + * Re-render an orbital at a higher resolution + * + * @param orbital The orbital to render + * @param resolution The resolution of the cube + */ + void calculateOrbitalFromWidget(unsigned int orbital, double resolution); + + /** + * Calculate all molecular orbitals at low priority and moderate + * resolution. + */ + void precalculateOrbitals(); + + /** + * Add an orbital calculation to the queue. Lower priority values + * run first. Do not set automatic calculations to priority zero, + * this is reserved for user requested calculations and will run + * first, displaying a progress dialog. + * + * @param orbital Orbital number + * @param resolution Resolution of grid + * @param isoval Isovalue for surface + * @param priority Priority. Default = 0 (user requested) + */ + void addCalculationToQueue(unsigned int orbital, double resolution, + double isoval, unsigned int priority = 0); + /** + * Check that no calculations are currently running and start the + * highest priority calculation. + */ + void checkQueue(); + + /** + * Start or resume the calculation at the indicated index of the + * queue. + */ + void startCalculation(unsigned int queueIndex); + + void calculateCube(); + void calculateCubeDone(); + void calculatePosMesh(); + void calculatePosMeshDone(); + void calculateNegMesh(); + void calculateNegMeshDone(); + void calculationComplete(); + void meshComplete(); + + /** + * Draw the indicated orbital on the GLWidget + */ + void renderOrbital(unsigned int orbital); + + /** + * Update the progress of the current calculation + */ + void updateProgress(int current); + +private: + QAction* m_action; + + QtGui::Molecule* m_molecule = nullptr; + Core::BasisSet* m_basis = nullptr; + + QList m_queue; + int m_currentRunningCalculation = -1; + int m_currentMeshCalculation = -1; + + QtGui::GaussianSetConcurrent* m_gaussianConcurrent = nullptr; + QtGui::SlaterSetConcurrent* m_slaterConcurrent = nullptr; + + QFutureWatcher m_displayMeshWatcher; + QtGui::MeshGenerator* m_meshGenerator = nullptr; + + float m_isoValue = 0.01; + int m_smoothingPasses = 1; + int m_meshesLeft = 0; + bool m_updateMesh = false; + + OrbitalWidget* m_dialog = nullptr; + // OrbitalSettingsDialog* m_orbitalSettingsDialog = nullptr; + QMutex* m_runningMutex = nullptr; +}; + +} // namespace QtPlugins +} // namespace Avogadro + +#endif // AVOGADRO_QTPLUGINS_ORBITALS_H diff --git a/avogadro/qtplugins/surfaces/orbitaltablemodel.cpp b/avogadro/qtplugins/surfaces/orbitaltablemodel.cpp new file mode 100644 index 0000000000..a6d1845986 --- /dev/null +++ b/avogadro/qtplugins/surfaces/orbitaltablemodel.cpp @@ -0,0 +1,308 @@ +/****************************************************************************** + This source file is part of the Avogadro project. + This source code is released under the 3-Clause BSD License, (see "LICENSE"). +******************************************************************************/ + +#include "orbitaltablemodel.h" +#include "orbitalwidget.h" + +#include +#include + +#include + +namespace Avogadro::QtPlugins { + +OrbitalTableModel::OrbitalTableModel(QWidget* parent) + : QAbstractTableModel(parent) +{ + m_orbitals.clear(); +} + +OrbitalTableModel::~OrbitalTableModel() {} + +int OrbitalTableModel::columnCount(const QModelIndex&) const +{ + return COUNT; +} + +QVariant OrbitalTableModel::data(const QModelIndex& index, int role) const +{ + if ((role != Qt::DisplayRole && role != Qt::TextAlignmentRole) || + !index.isValid()) + return QVariant(); + + // Simple lambda to convert QFlags to variant as in Qt 6 this needs help. + auto toVariant = [&](auto flags) { + return static_cast(flags); + }; + + if (role == Qt::TextAlignmentRole) { + switch (Column(index.column())) { + case C_Energy: + return toVariant(Qt::AlignRight | + Qt::AlignVCenter); // numeric alignment + case C_Status: // everything else can be centered + case C_Description: + case C_Symmetry: + default: + return toVariant(Qt::AlignHCenter | Qt::AlignVCenter); + } + } + + const Orbital* orb = m_orbitals.at(index.row()); + QString symbol; // use subscripts + int subscriptStart; + + switch (Column(index.column())) { + case C_Description: + return orb->description; + case C_Energy: + return QString("%L1").arg(orb->energy, 0, 'f', 3); + case C_Status: { + // Check for divide by zero + if (orb->max == orb->min) + return 0; + int percent = + 100 * (orb->current - orb->min) / float(orb->max - orb->min); + // Adjust for stages + int stages = (orb->totalStages == 0) ? 1 : orb->totalStages; + percent /= float(stages); + percent += (orb->stage - 1) * (100.0 / float(stages)); + // clamp to 100% + if (percent > 100) + percent = 100; + return QString("%L1 %").arg(percent); + } + case C_Symmetry: + symbol = orb->symmetry; + if (symbol.length() > 1) { + subscriptStart = 1; + if (symbol[0] == '?') + subscriptStart++; + symbol.insert(subscriptStart, QString("")); + symbol.append(QString("")); + } + symbol.replace('\'', QString("'")); + symbol.replace('"', QString("\"")); + return symbol; + default: + case COUNT: + return QVariant(); + } +} + +QVariant OrbitalTableModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) { + switch (Column(section)) { + case C_Description: + return tr("Orbital"); + case C_Energy: + return tr("Energy (eV)"); + case C_Symmetry: + return tr("Symmetry"); + case C_Status: + return tr("Status"); + default: + case COUNT: + return QVariant(); + } + } else + return QString::number(section + 1); +} + +QModelIndex OrbitalTableModel::HOMO() const +{ + for (int i = 0; i < m_orbitals.size(); i++) { + if (m_orbitals.at(i)->description == tr("HOMO", "Highest Occupied MO")) + return index(i, 0); + } + return QModelIndex(); +} + +QModelIndex OrbitalTableModel::LUMO() const +{ + for (int i = 0; i < m_orbitals.size(); i++) { + if (m_orbitals.at(i)->description == tr("LUMO", "Lowest Unoccupied MO")) + return index(i, 0); + } + return QModelIndex(); +} + +// predicate for sorting below +bool orbitalIndexLessThan(const Orbital* o1, const Orbital* o2) +{ + return (o1->index < o2->index); +} + +bool OrbitalTableModel::setOrbitals(const Core::BasisSet* basis) +{ + clearOrbitals(); + + // assemble the orbital information + // TODO: Alpha / Beta orbitals + unsigned int homo = basis->homo(); + unsigned int lumo = basis->lumo(); + unsigned int count = homo - 1; + bool leqHOMO = true; // orbital <= homo + + // energies and symmetries + // TODO: handle both alpha and beta (separate columns?) + // TODO: move moEnergies to the BasisSet class + QList alphaEnergies; + auto* gaussianBasis = dynamic_cast(basis); + if (gaussianBasis != nullptr) { + auto moEnergies = gaussianBasis->moEnergy(); + alphaEnergies.reserve(moEnergies.size()); + for (double energy : moEnergies) { + alphaEnergies.push_back(energy); + } + } + + // not sure if any import supports symmetry labels yet + const auto labels = basis->symmetryLabels(); + QStringList alphaSymmetries; + alphaSymmetries.reserve(labels.size()); + for (const std::string label : labels) { + alphaSymmetries.push_back(QString::fromStdString(label)); + } + + for (int i = 0; i < basis->molecularOrbitalCount(); i++) { + QString num = ""; + if (i + 1 != homo && i + 1 != lumo) { + num = (leqHOMO) ? "-" : "+"; + num += QString::number(count); + } + + QString desc = QString("%1") + // (HOMO|LUMO)(+|-)[0-9]+ + .arg((leqHOMO) ? tr("HOMO", "Highest Occupied MO") + num + : tr("LUMO", "Lowest Unoccupied MO") + num); + + Orbital* orb = new Orbital; + // Get the energy from the molecule property list, if available + if (alphaEnergies.size() > i) + orb->energy = alphaEnergies[i].toDouble(); + else + orb->energy = 0.0; + // symmetries (if available) + if (alphaSymmetries.size() > i) + orb->symmetry = alphaSymmetries[i]; + orb->index = i; + orb->description = desc; + orb->queueEntry = 0; + orb->min = 0; + orb->max = 0; + orb->current = 0; + + m_orbitals.append(orb); + if (i + 1 < homo) + count--; + else if (i + 1 == homo) + leqHOMO = false; + else if (i + 1 >= lumo) + count++; + } + // sort the orbital list (not sure if this is necessary) + std::sort(m_orbitals.begin(), m_orbitals.end(), orbitalIndexLessThan); + + // add the rows for all the new orbitals + beginInsertRows(QModelIndex(), 0, m_orbitals.size() - 1); + endInsertRows(); + return true; +} + +bool OrbitalTableModel::clearOrbitals() +{ + if (m_orbitals.size() > 0) { + beginRemoveRows(QModelIndex(), 0, m_orbitals.size() - 1); + m_orbitals.clear(); + endRemoveRows(); + } + + return true; +} + +void OrbitalTableModel::setOrbitalProgressRange(int orbital, int min, int max, + int stage, int totalStages) +{ + Orbital* orb = m_orbitals[orbital]; + orb->min = min; + orb->current = min; + orb->max = max; + orb->stage = stage; + orb->totalStages = totalStages; + // Update display + QModelIndex status = index(orbital, int(C_Status), QModelIndex()); + emit dataChanged(status, status); +} + +void OrbitalTableModel::incrementStage(int orbital, int newmin, int newmax) +{ + Orbital* orb = m_orbitals[orbital]; + orb->stage++; + orb->min = newmin; + orb->current = newmin; + orb->max = newmax; + // Update display + QModelIndex status = index(orbital, C_Status, QModelIndex()); + emit dataChanged(status, status); +} + +void OrbitalTableModel::setOrbitalProgressValue(int orbital, int currentValue) +{ + Orbital* orb = m_orbitals[orbital]; + orb->current = currentValue; + // Update display + QModelIndex status = index(orbital, C_Status, QModelIndex()); + emit dataChanged(status, status); +} + +void OrbitalTableModel::finishProgress(int orbital) +{ + Orbital* orb = m_orbitals[orbital]; + orb->stage = 1; + orb->totalStages = 1; + orb->min = 0; + orb->current = 1; + orb->max = 1; + + // Update display + QModelIndex status = index(orbital, C_Status, QModelIndex()); + emit dataChanged(status, status); +} + +void OrbitalTableModel::resetProgress(int orbital) +{ + Orbital* orb = m_orbitals[orbital]; + orb->stage = 1; + orb->totalStages = 1; + orb->min = 0; + orb->current = 0; + orb->max = 0; + + // Update display + QModelIndex status = index(orbital, C_Status, QModelIndex()); + emit dataChanged(status, status); +} + +void OrbitalTableModel::setProgressToZero(int orbital) +{ + Orbital* orb = m_orbitals[orbital]; + orb->stage = 1; + orb->totalStages = 1; + orb->min = 0; + orb->current = 0; + orb->max = 1; + + // Update display + QModelIndex status = index(orbital, C_Status, QModelIndex()); + emit dataChanged(status, status); +} + +} // namespace Avogadro::QtPlugins diff --git a/avogadro/qtplugins/surfaces/orbitaltablemodel.h b/avogadro/qtplugins/surfaces/orbitaltablemodel.h new file mode 100644 index 0000000000..5fe328425c --- /dev/null +++ b/avogadro/qtplugins/surfaces/orbitaltablemodel.h @@ -0,0 +1,140 @@ +/****************************************************************************** + This source file is part of the Avogadro project. + This source code is released under the 3-Clause BSD License, (see "LICENSE"). +******************************************************************************/ + +#ifndef AVOGADRO_QTPLUGINS_ORBITALTABLEMODEL_H +#define AVOGADRO_QTPLUGINS_ORBITALTABLEMODEL_H + +#include +#include +#include +#include + +namespace Avogadro::Core { +class BasisSet; +} + +namespace Avogadro::QtPlugins { + +struct calcInfo; + +struct Orbital +{ + double energy; + int index; + QString description; // (HOMO|LUMO)[(+|-)N] + QString symmetry; // e.g., A1g (with subscripts) + calcInfo* queueEntry; + // Progress data: + int min; + int max; + int current; + int stage; + int totalStages; +}; + +// Allow progress bars to be embedded in the table +class ProgressBarDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + ProgressBarDelegate(QObject* parent = 0) : QStyledItemDelegate(parent){}; + QSize sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const + { + return QSize(60, 30); + }; + + void paint(QPainter* p, const QStyleOptionViewItem& o, + const QModelIndex& ind) const + { + QStyleOptionProgressBar opt; + // Call initFrom() which will set the style based on the parent + // GRH: This is critical to get things right on Mac + // otherwise the status bars always look disabled + opt.initFrom(qobject_cast(this->parent())); + + opt.rect = o.rect; + opt.minimum = 1; // percentage + opt.maximum = 100; + opt.textVisible = true; + int percent = ind.model()->data(ind, Qt::DisplayRole).toInt(); + opt.progress = percent; + opt.text = QString("%1%").arg(QString::number(percent)); + QApplication::style()->drawControl(QStyle::CE_ProgressBar, &opt, p); + } +}; + +// Used for sorting: +class OrbitalSortingProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + OrbitalSortingProxyModel(QObject* parent = 0) + : QSortFilterProxyModel(parent), m_HOMOFirst(false){}; + + bool isHOMOFirst() { return m_HOMOFirst; }; + void HOMOFirst(bool b) { m_HOMOFirst = b; }; + +protected: + // Compare orbital values + bool lessThan(const QModelIndex& left, const QModelIndex& right) const + { + if (m_HOMOFirst) + return left.row() < right.row(); + else + return left.row() > right.row(); + } + +private: + bool m_HOMOFirst; +}; + +class OrbitalTableModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + enum Column + { + C_Description = 0, + C_Energy, + C_Symmetry, + C_Status, // also occupation (0/1/2) + + COUNT + }; + + //! Constructor + explicit OrbitalTableModel(QWidget* parent = 0); + //! Deconstructor + virtual ~OrbitalTableModel(); + + int rowCount(const QModelIndex&) const { return m_orbitals.size(); }; + int columnCount(const QModelIndex&) const; + + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + QModelIndex HOMO() const; + QModelIndex LUMO() const; + + bool setOrbitals(const Core::BasisSet* basis); + bool clearOrbitals(); + + // Stages are used for multi-step processes, e.g. cube, posmesh, negmesh, etc + void setOrbitalProgressRange(int orbital, int min, int max, int stage, + int totalStages); + void incrementStage(int orbital, int newmin, int newmax); + void setOrbitalProgressValue(int orbital, int currentValue); + void finishProgress(int orbital); + void resetProgress(int orbital); + void setProgressToZero(int orbital); + +private: + QList m_orbitals; +}; +} // namespace Avogadro::QtPlugins + +#endif diff --git a/avogadro/qtplugins/surfaces/orbitalwidget.cpp b/avogadro/qtplugins/surfaces/orbitalwidget.cpp new file mode 100644 index 0000000000..8d46a132bc --- /dev/null +++ b/avogadro/qtplugins/surfaces/orbitalwidget.cpp @@ -0,0 +1,235 @@ +/****************************************************************************** + This source file is part of the Avogadro project. + This source code is released under the 3-Clause BSD License, (see "LICENSE"). +******************************************************************************/ + +#include "orbitalwidget.h" +#include "orbitaltablemodel.h" + +#include + +#include +#include + +using Avogadro::QtGui::RichTextDelegate; + +namespace Avogadro::QtPlugins { + +OrbitalWidget::OrbitalWidget(QWidget* parent, Qt::WindowFlags f) + : QWidget(parent, f), m_settings(0), m_quality(OQ_Low), m_isovalue(0.03), + m_precalc_limit(true), m_precalc_range(10), + m_tableModel(new OrbitalTableModel(this)), + m_sortedTableModel(new OrbitalSortingProxyModel(this)) +{ + ui.setupUi(this); + + setWindowTitle(tr("Molecular Orbitals")); + + m_sortedTableModel->setSourceModel(m_tableModel); + + ui.table->setModel(m_sortedTableModel); + ui.table->horizontalHeader()->setSectionResizeMode( + QHeaderView::ResizeToContents); + ui.table->horizontalHeader()->setStretchLastSection(true); + // ui.table->setItemDelegateForColumn(OrbitalTableModel::C_Status, + // new ProgressBarDelegate(this)); + ui.table->setItemDelegateForColumn(OrbitalTableModel::C_Symmetry, + new RichTextDelegate(this)); + // TODO: Support orbital symmetry labels + ui.table->hideColumn(OrbitalTableModel::C_Symmetry); + ui.table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + connect( + ui.table->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SLOT(tableClicked(const QItemSelection&))); + connect(ui.push_render, SIGNAL(clicked()), this, SLOT(renderClicked())); + // TODO: Implement configure dialog + ui.push_configure->setVisible(false); + connect(ui.push_configure, SIGNAL(clicked()), this, SLOT(configureClicked())); + readSettings(); +} + +OrbitalWidget::~OrbitalWidget() +{ + writeSettings(); +} + +void OrbitalWidget::readSettings() +{ + QSettings settings; + settings.beginGroup("orbitals"); + m_quality = OrbitalQuality(settings.value("defaultQuality", 1).toInt()); + m_isovalue = settings.value("isoValue", 0.03).toDouble(); + ui.combo_quality->setCurrentIndex( + settings.value("selectedQuality", 1).toInt()); + m_sortedTableModel->HOMOFirst(settings.value("HOMOFirst", false).toBool()); + m_precalc_limit = settings.value("precalc/limit", true).toBool(); + m_precalc_range = settings.value("precalc/range", 10).toInt(); + settings.endGroup(); +} + +void OrbitalWidget::writeSettings() +{ + QSettings settings; + settings.beginGroup("orbitals"); + settings.setValue("defaultQuality", m_quality); + settings.setValue("isoValue", m_isovalue); + settings.setValue("selectedQuality", ui.combo_quality->currentIndex()); + settings.setValue("HOMOFirst", m_sortedTableModel->isHOMOFirst()); + settings.setValue("precalc/limit", m_precalc_limit); + settings.setValue("precalc/range", m_precalc_range); + settings.endGroup(); +} + +void OrbitalWidget::reject() +{ + hide(); +} + +void OrbitalWidget::configureClicked() +{ + /* + if (!m_settings) { + m_settings = new OrbitalSettingsDialog(this); + } + m_settings->setDefaultQuality(m_quality); + m_settings->setIsoValue(m_isovalue); + m_settings->setHOMOFirst(m_sortedTableModel->isHOMOFirst()); + m_settings->setLimitPrecalc(m_precalc_limit); + m_settings->setPrecalcRange(m_precalc_range); + m_settings->show(); + */ +} + +void OrbitalWidget::fillTable(Core::BasisSet* basis) +{ + if (basis == nullptr || m_tableModel == nullptr) { + return; + } + + // Populate the model + m_tableModel->setOrbitals(basis); + + ui.table->horizontalHeader()->sectionResizeMode( + QHeaderView::ResizeToContents); + + // Sort table + m_sortedTableModel->sort(0, Qt::AscendingOrder); + + // // Find HOMO and scroll to it + QModelIndex homo = m_tableModel->HOMO(); + homo = m_sortedTableModel->mapFromSource(homo); + // qDebug() << "HOMO at: " << homo.row(); + ui.table->scrollTo(homo, QAbstractItemView::PositionAtCenter); +} + +void OrbitalWidget::setQuality(OrbitalQuality q) +{ + ui.combo_quality->setCurrentIndex(int(q)); +} + +void OrbitalWidget::selectOrbital(unsigned int orbital) +{ + QModelIndex start = m_tableModel->index(orbital - 1, 0, QModelIndex()); + QModelIndex end = m_tableModel->index( + orbital - 1, m_tableModel->columnCount(QModelIndex()) - 1, QModelIndex()); + QItemSelection selection(start, end); + + selection = m_sortedTableModel->mapSelectionFromSource(selection); + + ui.table->selectionModel()->clear(); + ui.table->selectionModel()->select(selection, + QItemSelectionModel::SelectCurrent); +} + +void OrbitalWidget::tableClicked(const QItemSelection& selected) +{ + QItemSelection mappedSelected = + m_sortedTableModel->mapSelectionToSource(selected); + + QModelIndexList selection = mappedSelected.indexes(); + + // Only one row can be selected at a time, so just check the row + // of the first entry. + if (selection.size() == 0) + return; + int orbital = selection.first().row() + 1; + emit orbitalSelected(orbital); +} + +void OrbitalWidget::renderClicked() +{ + double quality = OrbitalQualityToDouble(ui.combo_quality->currentIndex()); + QModelIndexList selection = ui.table->selectionModel()->selectedIndexes(); + + // Only one row can be selected at a time, so just check the row + // of the first entry. + if (selection.size() == 0) + return; + + QModelIndex first = selection.first(); + first = m_sortedTableModel->mapToSource(first); + + int orbital = first.row() + 1; + emit renderRequested(orbital, quality); +} + +double OrbitalWidget::OrbitalQualityToDouble(OrbitalQuality q) +{ + switch (q) { + case OQ_VeryLow: + return 0.5; + case OQ_Low: + return 0.35; + case OQ_Medium: + default: + return 0.18; + case OQ_High: + return 0.10; + case OQ_VeryHigh: + return 0.05; + } +} + +void OrbitalWidget::setDefaults(OrbitalQuality q, double i, bool HOMOFirst) +{ + m_quality = q; + m_isovalue = i; + m_sortedTableModel->HOMOFirst(HOMOFirst); + m_sortedTableModel->sort(0, Qt::AscendingOrder); +} + +void OrbitalWidget::setPrecalcSettings(bool limit, int range) +{ + m_precalc_limit = limit; + m_precalc_range = range; +} + +void OrbitalWidget::initializeProgress(int orbital, int min, int max, int stage, + int totalStages) +{ + m_tableModel->setOrbitalProgressRange(orbital, min, max, stage, totalStages); +} + +void OrbitalWidget::nextProgressStage(int orbital, int newmin, int newmax) +{ + m_tableModel->incrementStage(orbital, newmin, newmax); +} + +void OrbitalWidget::updateProgress(int orbital, int current) +{ + m_tableModel->setOrbitalProgressValue(orbital, current); +} + +void OrbitalWidget::calculationComplete(int orbital) +{ + m_tableModel->finishProgress(orbital); +} + +void OrbitalWidget::calculationQueued(int orbital) +{ + m_tableModel->setProgressToZero(orbital); +} + +} // namespace Avogadro::QtPlugins diff --git a/avogadro/qtplugins/surfaces/orbitalwidget.h b/avogadro/qtplugins/surfaces/orbitalwidget.h new file mode 100644 index 0000000000..be9fe94b4d --- /dev/null +++ b/avogadro/qtplugins/surfaces/orbitalwidget.h @@ -0,0 +1,98 @@ +/****************************************************************************** + This source file is part of the Avogadro project. + This source code is released under the 3-Clause BSD License, (see "LICENSE"). +******************************************************************************/ + +#ifndef AVOGADRO_QTPLUGINS_ORBITALWIDGET_H +#define AVOGADRO_QTPLUGINS_ORBITALWIDGET_H + +#include + +#include "orbitaltablemodel.h" +#include "ui_orbitalwidget.h" + +namespace Avogadro::Core { +class BasisSet; +} + +namespace Avogadro::QtPlugins { + +class OrbitalSettingsDialog; +class OrbitalTableModel; + +class OrbitalWidget : public QWidget +{ + Q_OBJECT + +public: + enum OrbitalQuality + { + OQ_VeryLow = 0, + OQ_Low, + OQ_Medium, + OQ_High, + OQ_VeryHigh + }; + + //! Constructor + explicit OrbitalWidget(QWidget* parent = nullptr, + Qt::WindowFlags f = Qt::Widget); + //! Deconstructor + virtual ~OrbitalWidget(); + + double isovalue() { return m_isovalue; }; + OrbitalQuality defaultQuality() { return m_quality; }; + + bool precalcLimit() { return m_precalc_limit; } + int precalcRange() { return m_precalc_range; } + + static double OrbitalQualityToDouble(OrbitalQuality q); + static double OrbitalQualityToDouble(int i) + { + return OrbitalQualityToDouble(OrbitalQuality(i)); + }; + +public slots: + void readSettings(); + void writeSettings(); + void reject(); + + void fillTable(Core::BasisSet* basis); + void setQuality(OrbitalQuality q); + void selectOrbital(unsigned int orbital); + void setDefaults(OrbitalWidget::OrbitalQuality quality, double isovalue, + bool HOMOFirst); + void setPrecalcSettings(bool limit, int range); + void initializeProgress(int orbital, int min, int max, int stage, + int totalStages); + void nextProgressStage(int orbital, int newmin, int newmax); + void updateProgress(int orbital, int current); + void calculationComplete(int orbital); + void calculationQueued(int orbital); + +signals: + void orbitalSelected(unsigned int orbital); + void renderRequested(unsigned int orbital, double resolution); + void calculateAll(); + +private slots: + void tableClicked(const QItemSelection&); + void renderClicked(); + void configureClicked(); + +private: + Ui::OrbitalWidget ui; + OrbitalSettingsDialog* m_settings; + OrbitalQuality m_quality; + double m_isovalue; + + bool m_precalc_limit; + int m_precalc_range; + + OrbitalTableModel* m_tableModel; + OrbitalSortingProxyModel* m_sortedTableModel; +}; + +} // namespace Avogadro::QtPlugins + +#endif diff --git a/avogadro/qtplugins/surfaces/orbitalwidget.ui b/avogadro/qtplugins/surfaces/orbitalwidget.ui new file mode 100644 index 0000000000..10fbb45ff9 --- /dev/null +++ b/avogadro/qtplugins/surfaces/orbitalwidget.ui @@ -0,0 +1,138 @@ + + + OrbitalWidget + + + + 0 + 0 + 366 + 696 + + + + + 0 + 0 + + + + Form + + + + + + + 0 + 0 + + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerItem + + + true + + + true + + + true + + + + + + + + + + 0 + 0 + + + + Quality: + + + + + + + 0 + + + + Very Low + + + + + Low + + + + + Medium + + + + + High + + + + + Very High + + + + + + + + Render + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Configure + + + + + + + + + + diff --git a/avogadro/qtplugins/surfaces/surfaces.cpp b/avogadro/qtplugins/surfaces/surfaces.cpp index ccdf629a17..35d0a06254 100644 --- a/avogadro/qtplugins/surfaces/surfaces.cpp +++ b/avogadro/qtplugins/surfaces/surfaces.cpp @@ -6,9 +6,6 @@ #include "surfaces.h" #include "surfacedialog.h" -#include "gaussiansetconcurrent.h" -#include "slatersetconcurrent.h" - // Header only, but duplicate symbols if included globally... namespace { #include @@ -25,9 +22,11 @@ namespace { #include #include #include +#include #include #include #include +#include #include #include @@ -68,7 +67,9 @@ using Core::Array; using Core::Cube; using Core::GaussianSet; using Core::NeighborPerceiver; +using QtGui::GaussianSetConcurrent; using QtGui::Molecule; +using QtGui::SlaterSetConcurrent; class Surfaces::PIMPL { diff --git a/avogadro/qtplugins/surfaces/surfaces.h b/avogadro/qtplugins/surfaces/surfaces.h index ee476ca3da..3e3088f959 100644 --- a/avogadro/qtplugins/surfaces/surfaces.h +++ b/avogadro/qtplugins/surfaces/surfaces.h @@ -22,7 +22,9 @@ namespace Avogadro { namespace QtGui { class MeshGenerator; -} +class GaussianSetConcurrent; +class SlaterSetConcurrent; +} // namespace QtGui namespace Core { class BasisSet; @@ -37,8 +39,6 @@ namespace QtPlugins { * menu entries to calculate surfaces, including QM ones. */ -class GaussianSetConcurrent; -class SlaterSetConcurrent; class SurfaceDialog; class Surfaces : public QtGui::ExtensionPlugin @@ -119,8 +119,8 @@ private slots: QtGui::Molecule* m_molecule = nullptr; Core::BasisSet* m_basis = nullptr; - GaussianSetConcurrent* m_gaussianConcurrent = nullptr; - SlaterSetConcurrent* m_slaterConcurrent = nullptr; + QtGui::GaussianSetConcurrent* m_gaussianConcurrent = nullptr; + QtGui::SlaterSetConcurrent* m_slaterConcurrent = nullptr; Core::Cube* m_cube = nullptr; std::vector m_cubes; @@ -152,4 +152,4 @@ private slots: } // namespace QtPlugins } // namespace Avogadro -#endif // AVOGADRO_QTPLUGINS_QUANTUMOUTPUT_H +#endif // AVOGADRO_QTPLUGINS_SURFACES_H