From d01fb8af33818fae9a5edf378d2340c9fd4366db Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Thu, 28 Nov 2024 14:47:28 -0500 Subject: [PATCH 1/8] Starting updates for NMR and UV spectra Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/spectra/spectra.cpp | 14 ++ avogadro/qtplugins/spectra/spectradialog.ui | 138 +++++++++++--------- 2 files changed, 90 insertions(+), 62 deletions(-) diff --git a/avogadro/qtplugins/spectra/spectra.cpp b/avogadro/qtplugins/spectra/spectra.cpp index 39b424334c..5fc0808e7e 100644 --- a/avogadro/qtplugins/spectra/spectra.cpp +++ b/avogadro/qtplugins/spectra/spectra.cpp @@ -21,6 +21,20 @@ namespace Avogadro::QtPlugins { +const QMap Spectra::NMR = { + 1, "¹H", + 3, "⁷Li", + 4, "¹¹Be", + 6, "¹³C", + 7, "¹⁵N", + 8, "¹⁷O", + 9, "¹⁹F", + 14, "²⁹Si", + 15, "³¹P", + // add any other common nuclei here + // otherwise we map from the element symbol +} + Spectra::Spectra(QObject* p) : ExtensionPlugin(p), m_molecule(nullptr), m_dialog(nullptr) { diff --git a/avogadro/qtplugins/spectra/spectradialog.ui b/avogadro/qtplugins/spectra/spectradialog.ui index 2d0574af20..6277565fd2 100644 --- a/avogadro/qtplugins/spectra/spectradialog.ui +++ b/avogadro/qtplugins/spectra/spectradialog.ui @@ -23,43 +23,7 @@ true - - - - false - - - - 0 - 0 - - - - &Load data... - - - - - - - Export Data - - - - - - - - 0 - 0 - - - - &Close - - - - + @@ -461,7 +425,50 @@ - + + + + + 0 + 0 + + + + + + + + Export Data + + + + + + + + 0 + 0 + + + + &Close + + + + + + + + 0 + 0 + + + + &Options… + + + + @@ -517,30 +524,7 @@ - - - - - 0 - 0 - - - - &Options… - - - - - - - - 0 - 0 - - - - - + @@ -572,6 +556,36 @@ Scroll wheel: Zoom to cursor + + + + false + + + + 0 + 0 + + + + &Load data... + + + + + + + + ¹H + + + + + ¹³C + + + + From 6aa5d0420173029b7adba52fe34e3709afe07051 Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Thu, 12 Dec 2024 10:58:08 -0500 Subject: [PATCH 2/8] Some work to filter NMR spectra by element Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/spectra/spectra.cpp | 14 -- avogadro/qtplugins/spectra/spectradialog.cpp | 153 ++++++++++++++++++- avogadro/qtplugins/spectra/spectradialog.h | 10 +- avogadro/qtplugins/spectra/spectradialog.ui | 14 ++ 4 files changed, 172 insertions(+), 19 deletions(-) diff --git a/avogadro/qtplugins/spectra/spectra.cpp b/avogadro/qtplugins/spectra/spectra.cpp index 5fc0808e7e..39b424334c 100644 --- a/avogadro/qtplugins/spectra/spectra.cpp +++ b/avogadro/qtplugins/spectra/spectra.cpp @@ -21,20 +21,6 @@ namespace Avogadro::QtPlugins { -const QMap Spectra::NMR = { - 1, "¹H", - 3, "⁷Li", - 4, "¹¹Be", - 6, "¹³C", - 7, "¹⁵N", - 8, "¹⁷O", - 9, "¹⁹F", - 14, "²⁹Si", - 15, "³¹P", - // add any other common nuclei here - // otherwise we map from the element symbol -} - Spectra::Spectra(QObject* p) : ExtensionPlugin(p), m_molecule(nullptr), m_dialog(nullptr) { diff --git a/avogadro/qtplugins/spectra/spectradialog.cpp b/avogadro/qtplugins/spectra/spectradialog.cpp index 730c4484dc..4640a8eb02 100644 --- a/avogadro/qtplugins/spectra/spectradialog.cpp +++ b/avogadro/qtplugins/spectra/spectradialog.cpp @@ -55,6 +55,14 @@ SpectraDialog::SpectraDialog(QWidget* parent) : QDialog(parent), m_ui(new Ui::SpectraDialog) { m_ui->setupUi(this); + + // hide the units for now + m_ui->unitsLabel->hide(); + m_ui->unitsCombo->hide(); + + // only for NMR + m_ui->elementCombo->hide(); + m_ui->dataTable->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch); @@ -98,6 +106,65 @@ SpectraDialog::~SpectraDialog() writeSettings(); } +void SpectraDialog::updateElementCombo() +{ + // update the element combo box + disconnect(m_ui->elementCombo, SIGNAL(currentIndexChanged(int)), this, + SLOT(changeSpectra())); + m_ui->elementCombo->clear(); + + // add the unique elements, with the element number as the data + for (auto& element : m_elements) { + // check to see if it's already in the combo box + bool found = false; + for (int i = 0; i < m_ui->elementCombo->count(); ++i) { + if (m_ui->elementCombo->itemData(i).toInt() == element) { + found = true; + break; + } + } + if (found) + continue; + + switch (element) { + case 1: + m_ui->elementCombo->addItem("¹H", element); + break; + case 3: + m_ui->elementCombo->addItem("⁷Li", element); + break; + case 5: + m_ui->elementCombo->addItem("¹¹B", element); + break; + case 6: + m_ui->elementCombo->addItem("¹³C", element); + break; + case 7: + m_ui->elementCombo->addItem("¹⁵N", element); + break; + case 8: + m_ui->elementCombo->addItem("¹⁷O", element); + break; + case 9: + m_ui->elementCombo->addItem("¹⁹F", element); + break; + case 14: + m_ui->elementCombo->addItem("²⁹Si", element); + break; + case 15: + m_ui->elementCombo->addItem("³¹P", element); + break; + default: + m_ui->elementCombo->addItem(QString::number(element), element); + break; + } + } + + // connect the element combo box + connect(m_ui->elementCombo, SIGNAL(currentIndexChanged(int)), this, + SLOT(changeSpectra())); +} + void SpectraDialog::changeSpectra() { // TODO: change the scale and offset based on defaults and settings @@ -107,6 +174,8 @@ void SpectraDialog::changeSpectra() SpectraType type = static_cast(m_ui->combo_spectra->currentData().toInt()); + m_ui->elementCombo->hide(); + switch (type) { case SpectraType::Infrared: m_ui->scaleSpinBox->setValue(1.0); @@ -126,9 +195,8 @@ void SpectraDialog::changeSpectra() m_ui->scaleSpinBox->setValue(1.0); m_ui->offsetSpinBox->setValue(0.0); // todo: these should be per element - m_ui->xAxisMinimum->setValue(0.0); - m_ui->xAxisMaximum->setValue(200.0); m_ui->peakWidth->setValue(0.1); + m_ui->elementCombo->show(); break; case SpectraType::Electronic: m_ui->scaleSpinBox->setValue(1.0); @@ -141,6 +209,7 @@ void SpectraDialog::changeSpectra() case SpectraType::CircularDichroism: m_ui->scaleSpinBox->setValue(1.0); m_ui->offsetSpinBox->setValue(0.0); + // default to eV m_ui->xAxisMinimum->setValue(5.0); m_ui->xAxisMaximum->setValue(1.0); m_ui->peakWidth->setValue(0.1); @@ -154,8 +223,84 @@ void SpectraDialog::changeSpectra() break; } - MatrixX& spectra = - m_spectra[m_ui->combo_spectra->currentText().toStdString()]; + // a bunch of special work depending on the NMR element + if (type == SpectraType::NMR) { + // get the element + int element = m_ui->elementCombo->currentData().toInt(); + + qDebug() << " NMR element: " << element; + + // tweak the default axis range + // based on https://imserc.northwestern.edu/guide/eNMR/chem/NMRnuclei.html + switch (element) { + case 1: // 1H + m_ui->xAxisMinimum->setValue(12.0); + m_ui->xAxisMaximum->setValue(0.0); + break; + case 3: // 7Li + m_ui->xAxisMinimum->setValue(-16.0); + m_ui->xAxisMaximum->setValue(11.0); + break; + case 5: // 11B + m_ui->xAxisMinimum->setValue(100.0); + m_ui->xAxisMaximum->setValue(-120.0); + break; + case 6: // 13C + m_ui->xAxisMinimum->setValue(200.0); + m_ui->xAxisMaximum->setValue(0.0); + break; + case 7: // 15N + m_ui->xAxisMinimum->setValue(800.0); + m_ui->xAxisMaximum->setValue(0.0); + break; + case 8: // 17O + m_ui->xAxisMinimum->setValue(1600.0); + m_ui->xAxisMaximum->setValue(-50.0); + break; + case 9: // 19F + m_ui->xAxisMinimum->setValue(60.0); + m_ui->xAxisMaximum->setValue(-300.0); + break; + case 14: // 29Si + m_ui->xAxisMinimum->setValue(50.0); + m_ui->xAxisMaximum->setValue(-200.0); + break; + case 15: // 31P + m_ui->xAxisMinimum->setValue(250.0); + m_ui->xAxisMaximum->setValue(-250.0); + break; + default: + m_ui->xAxisMinimum->setValue(100.0); + m_ui->xAxisMaximum->setValue(-100.0); + break; + } + + // the default NMR data has all the atoms in it, + // so we need to loop through m_elements to filter + MatrixX nmr = m_spectra["NMR"]; + + qDebug() << " NMR size: " << nmr.rows() << "x" << nmr.cols(); + qDebug() << " m_elements size: " << m_elements.size(); + + std::vector nmrPeaks; + for (int i = 0; i < m_elements.size(); ++i) { + if (m_elements[i] == element) { + nmrPeaks[i] = nmr(i, 0); + } + } + MatrixX newNMR(nmrPeaks.size(), 2); + for (int i = 0; i < nmrPeaks.size(); ++i) { + newNMR(i, 0) = nmrPeaks[i]; + newNMR(i, 1) = 1.0; + } + + m_currentSpectra = newNMR; + } else { + m_currentSpectra = + m_spectra[m_ui->combo_spectra->currentText().toStdString()]; + } + + MatrixX& spectra = m_currentSpectra; float maxIntensity = 1.0; // update the data table m_ui->dataTable->setRowCount(spectra.rows()); diff --git a/avogadro/qtplugins/spectra/spectradialog.h b/avogadro/qtplugins/spectra/spectradialog.h index 4b96a74a4a..6f08459fae 100644 --- a/avogadro/qtplugins/spectra/spectradialog.h +++ b/avogadro/qtplugins/spectra/spectradialog.h @@ -45,6 +45,11 @@ class SpectraDialog : public QDialog void readSettings(); void setSpectra(const std::map& spectra); + void setElements(const std::vector& elements) + { + m_elements = elements; + updateElementCombo(); + } VTK::ChartWidget* chartWidget(); @@ -57,14 +62,17 @@ private slots: void changeLineWidth(); void changeSpectra(); + void updateElementCombo(); void updatePlot(); void toggleOptions(); private: std::map m_spectra; + std::vector m_elements; // for NMR + MatrixX m_currentSpectra; - QString m_currentSpectra; + QString m_currentSpectraType; Ui::SpectraDialog* m_ui; }; diff --git a/avogadro/qtplugins/spectra/spectradialog.ui b/avogadro/qtplugins/spectra/spectradialog.ui index 6277565fd2..ef7bfc73ad 100644 --- a/avogadro/qtplugins/spectra/spectradialog.ui +++ b/avogadro/qtplugins/spectra/spectradialog.ui @@ -181,6 +181,20 @@ + + + + Units: + + + + + + + false + + + From fdd4e505fd5398da11a5e28daf94d0a4d501e16d Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Wed, 18 Dec 2024 23:46:52 -0500 Subject: [PATCH 3/8] Make sure to store electronic and CD spectra Signed-off-by: Geoff Hutchison --- avogadro/quantumio/orca.cpp | 39 +++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/avogadro/quantumio/orca.cpp b/avogadro/quantumio/orca.cpp index c1fe93b398..ae9a5758ef 100644 --- a/avogadro/quantumio/orca.cpp +++ b/avogadro/quantumio/orca.cpp @@ -60,6 +60,23 @@ bool ORCAOutput::read(std::istream& in, Core::Molecule& molecule) return false; } + // this should be the final coordinate set (e.g. the optimized geometry) + molecule.setCoordinate3d(molecule.atomPositions3d(), 0); + if (m_coordSets.size() > 1) { + for (unsigned int i = 0; i < m_coordSets.size(); i++) { + Array positions; + positions.reserve(molecule.atomCount()); + for (size_t j = 0; j < molecule.atomCount(); ++j) { + positions.push_back(m_coordSets[i][j] * BOHR_TO_ANGSTROM); + } + molecule.setCoordinate3d(positions, i + 1); + } + } + + // guess bonds and bond orders + molecule.perceiveBondsSimple(); + molecule.perceiveBondOrders(); + if (m_frequencies.size() > 0 && m_frequencies.size() == m_vibDisplacements.size() && m_frequencies.size() == m_IRintensities.size()) { @@ -98,23 +115,6 @@ bool ORCAOutput::read(std::istream& in, Core::Molecule& molecule) molecule.setSpectra("NMR", nmrData); } - // this should be the final coordinate set (e.g. the optimized geometry) - molecule.setCoordinate3d(molecule.atomPositions3d(), 0); - if (m_coordSets.size() > 1) { - for (unsigned int i = 0; i < m_coordSets.size(); i++) { - Array positions; - positions.reserve(molecule.atomCount()); - for (size_t j = 0; j < molecule.atomCount(); ++j) { - positions.push_back(m_coordSets[i][j] * BOHR_TO_ANGSTROM); - } - molecule.setCoordinate3d(positions, i + 1); - } - } - - // guess bonds and bond orders - molecule.perceiveBondsSimple(); - molecule.perceiveBondOrders(); - // check bonds from calculated bond orders if (m_bondOrders.size() > 0) { for (unsigned int i = 0; i < m_bondOrders.size(); i++) { @@ -251,7 +251,8 @@ void ORCAOutput::processLine(std::istream& in, GaussianSet* basis) getline(in, key); // skip header } // starts at the next line - } else if (Core::contains(key, "CD SPECTRUM")) { + } else if (Core::contains(key, "CD SPECTRUM") && + !Core::contains(key, "TRANSITION VELOCITY DIPOLE")) { m_currentMode = ECD; for (int i = 0; i < 4; ++i) { getline(in, key); // skip header @@ -697,7 +698,7 @@ void ORCAOutput::processLine(std::istream& in, GaussianSet* basis) wavenumbers = Core::lexicalCast(list[1]); // convert to eV - m_electronicTransitions.push_back(wavenumbers / 8065.544); + // m_electronicTransitions.push_back(wavenumbers / 8065.544); m_electronicRotations.push_back(Core::lexicalCast(list[3])); getline(in, key); From 8e4ea5ab7f01d2c97a8a83c51a6dc9eefc111654 Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Wed, 18 Dec 2024 23:47:11 -0500 Subject: [PATCH 4/8] Initial update for electronic and CD spectra plots Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/spectra/spectradialog.cpp | 130 +++++++++++++------ avogadro/qtplugins/spectra/spectradialog.h | 3 + avogadro/qtplugins/spectra/spectradialog.ui | 3 + 3 files changed, 94 insertions(+), 42 deletions(-) diff --git a/avogadro/qtplugins/spectra/spectradialog.cpp b/avogadro/qtplugins/spectra/spectradialog.cpp index 730c4484dc..8da1927b92 100644 --- a/avogadro/qtplugins/spectra/spectradialog.cpp +++ b/avogadro/qtplugins/spectra/spectradialog.cpp @@ -63,6 +63,8 @@ SpectraDialog::SpectraDialog(QWidget* parent) m_ui->dataTable->hide(); m_ui->push_exportData->hide(); + readSettings(); + // connections for options connect(m_ui->push_options, SIGNAL(clicked()), this, SLOT(toggleOptions())); connect(m_ui->push_colorBackground, SIGNAL(clicked()), this, @@ -77,6 +79,17 @@ SpectraDialog::SpectraDialog(QWidget* parent) SLOT(changeFontSize())); connect(m_ui->lineWidthSpinBox, SIGNAL(valueChanged(double)), this, SLOT(changeLineWidth())); + connectOptions(); +} + +SpectraDialog::~SpectraDialog() +{ + writeSettings(); +} + +void SpectraDialog::connectOptions() +{ + // connect (or reconnect) anything that calls change or update plot connect(m_ui->combo_spectra, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSpectra())); connect(m_ui->xAxisMinimum, SIGNAL(valueChanged(double)), this, @@ -89,13 +102,23 @@ SpectraDialog::SpectraDialog(QWidget* parent) SLOT(updatePlot())); connect(m_ui->peakWidth, SIGNAL(valueChanged(double)), this, SLOT(updatePlot())); - - readSettings(); } -SpectraDialog::~SpectraDialog() +void SpectraDialog::disconnectOptions() { - writeSettings(); + // disconnect anything that calls change or update plot + disconnect(m_ui->combo_spectra, SIGNAL(currentIndexChanged(int)), this, + SLOT(changeSpectra())); + disconnect(m_ui->xAxisMinimum, SIGNAL(valueChanged(double)), this, + SLOT(updatePlot())); + disconnect(m_ui->xAxisMaximum, SIGNAL(valueChanged(double)), this, + SLOT(updatePlot())); + disconnect(m_ui->yAxisMinimum, SIGNAL(valueChanged(double)), this, + SLOT(updatePlot())); + disconnect(m_ui->yAxisMaximum, SIGNAL(valueChanged(double)), this, + SLOT(updatePlot())); + disconnect(m_ui->peakWidth, SIGNAL(valueChanged(double)), this, + SLOT(updatePlot())); } void SpectraDialog::changeSpectra() @@ -103,6 +126,8 @@ void SpectraDialog::changeSpectra() // TODO: change the scale and offset based on defaults and settings QSettings settings; + disconnectOptions(); + // what type of spectra are we plotting? SpectraType type = static_cast(m_ui->combo_spectra->currentData().toInt()); @@ -154,38 +179,8 @@ void SpectraDialog::changeSpectra() break; } - MatrixX& spectra = - m_spectra[m_ui->combo_spectra->currentText().toStdString()]; - float maxIntensity = 1.0; - // update the data table - m_ui->dataTable->setRowCount(spectra.rows()); - m_ui->dataTable->setColumnCount(spectra.cols()); - for (auto i = 0; i < spectra.rows(); ++i) { - for (auto j = 0; j < spectra.cols(); ++j) { - QTableWidgetItem* item = - new QTableWidgetItem(QString::number(spectra(i, j), 'f', 4)); - m_ui->dataTable->setItem(i, j, item); - } - } - // if there's a second column, check for intensities - if (spectra.cols() > 1) { - for (auto i = 0; i < spectra.rows(); ++i) { - if (spectra(i, 1) > maxIntensity) - maxIntensity = spectra(i, 1); - } - maxIntensity = maxIntensity * 1.25; - } - // if transmission for IR, set the max intensity to 100 - if (type == SpectraType::Infrared) - maxIntensity = 100.0; - - if (maxIntensity < 1.0) - maxIntensity = 1.0; - - // update the spin box - m_ui->yAxisMaximum->setValue(maxIntensity); - updatePlot(); + connectOptions(); } void SpectraDialog::setSpectra(const std::map& spectra) @@ -193,9 +188,13 @@ void SpectraDialog::setSpectra(const std::map& spectra) m_spectra = spectra; // update the combo box + disconnect(m_ui->combo_spectra, SIGNAL(currentIndexChanged(int)), this, + SLOT(changeSpectra())); + m_ui->combo_spectra->clear(); for (auto& spectra : m_spectra) { QString name = QString::fromStdString(spectra.first); + if (name == "IR") { name = tr("Infrared"); m_ui->combo_spectra->addItem(name, @@ -222,7 +221,9 @@ void SpectraDialog::setSpectra(const std::map& spectra) } changeSpectra(); - updatePlot(); + // connect again + connect(m_ui->combo_spectra, SIGNAL(currentIndexChanged(int)), this, + SLOT(changeSpectra())); } void SpectraDialog::writeSettings() const @@ -319,6 +320,9 @@ void SpectraDialog::changeLineWidth() void SpectraDialog::updatePlot() { + // disconnect the options while we update the plot + disconnectOptions(); + // the raw data std::vector transitions, intensities; // for the plot @@ -392,7 +396,11 @@ void SpectraDialog::updatePlot() break; case SpectraType::CircularDichroism: transitions = fromMatrix(m_spectra["Electronic"].col(0)); - intensities = fromMatrix(m_spectra["Electronic"].col(2)); + // check if electronic has a third column + if (m_spectra["Electronic"].cols() > 2) + intensities = fromMatrix(m_spectra["Electronic"].col(2)); + else // grab it from the CD data + intensities = fromMatrix(m_spectra["CircularDichroism"].col(1)); windowName = tr("Circular Dichroism Spectra"); xTitle = tr("eV)"); yTitle = tr("Intensity"); @@ -419,12 +427,41 @@ void SpectraDialog::updatePlot() } setWindowTitle(windowName); + // update the data table + m_ui->dataTable->setRowCount(transitions.size()); + m_ui->dataTable->setColumnCount(2); + for (auto i = 0; i < transitions.size(); ++i) { + // frequency or energy + QTableWidgetItem* item = + new QTableWidgetItem(QString::number(transitions[i], 'f', 4)); + m_ui->dataTable->setItem(i, 0, item); + // intensities + item = new QTableWidgetItem(QString::number(intensities[i], 'f', 4)); + m_ui->dataTable->setItem(i, 1, item); + } + double maxIntensity = 0.0f; for (auto intensity : intensities) { if (intensity > maxIntensity) maxIntensity = intensity; } + // if transmission for IR, set the max intensity to 100 + if (type == SpectraType::Infrared) + maxIntensity = 100.0; + + if (maxIntensity < 1.0) + maxIntensity = 1.0; + + // update the spin boxes + m_ui->yAxisMaximum->setValue(maxIntensity); + m_ui->yAxisMinimum->setMinimum(0.0); + // if CD, set the minimum too + if (type == SpectraType::CircularDichroism) { + m_ui->yAxisMinimum->setMinimum(-maxIntensity * 2.0); + m_ui->yAxisMinimum->setValue(-maxIntensity); + } + // now compose the plot data float scale = m_ui->scaleSpinBox->value(); float offset = m_ui->offsetSpinBox->value(); @@ -433,11 +470,17 @@ void SpectraDialog::updatePlot() float xMin = m_ui->xAxisMinimum->value(); float xMax = m_ui->xAxisMaximum->value(); - int start = std::min(static_cast(xMin), static_cast(xMax)); - int end = std::max(static_cast(xMin), static_cast(xMax)); + float start = std::min(xMin, xMax); + float end = std::max(xMin, xMax); + // for some spectra, we need to take small steps, so we scale the x axis + float xScale = 1.0; + if (type == SpectraType::Electronic || type == SpectraType::CircularDichroism) + xScale = 1.0f / 0.05f; + + float stickWidth = fwhm / (xScale * 30.0); - for (unsigned int x = start; x < end; ++x) { - float xValue = static_cast(x); + for (unsigned int x = round(start * xScale); x < round(end * xScale); ++x) { + float xValue = static_cast(x) / xScale; xData.push_back(xValue); yData.push_back(0.0f); yStick.push_back(0.0f); @@ -448,7 +491,7 @@ void SpectraDialog::updatePlot() float peak = intensities[index]; float intensity = scaleAndBlur(xValue, freq, peak, scale, offset, fwhm); - float stick = scaleAndBlur(xValue, freq, peak, scale, offset, 1.0); + float stick = scaleAndBlur(xValue, freq, peak, scale, offset, stickWidth); yData.back() += intensity; yStick.back() += stick; @@ -492,6 +535,9 @@ void SpectraDialog::updatePlot() chart->setXAxisLimits(xAxisMin, xAxisMax); chart->setYAxisLimits(yAxisMin, yAxisMax); + + // re-enable the options + connectOptions(); } VTK::ChartWidget* SpectraDialog::chartWidget() diff --git a/avogadro/qtplugins/spectra/spectradialog.h b/avogadro/qtplugins/spectra/spectradialog.h index 4b96a74a4a..db00eee1f4 100644 --- a/avogadro/qtplugins/spectra/spectradialog.h +++ b/avogadro/qtplugins/spectra/spectradialog.h @@ -48,6 +48,9 @@ class SpectraDialog : public QDialog VTK::ChartWidget* chartWidget(); + void disconnectOptions(); + void connectOptions(); + private slots: void changeBackgroundColor(); void changeForegroundColor(); diff --git a/avogadro/qtplugins/spectra/spectradialog.ui b/avogadro/qtplugins/spectra/spectradialog.ui index 2d0574af20..16125c45a7 100644 --- a/avogadro/qtplugins/spectra/spectradialog.ui +++ b/avogadro/qtplugins/spectra/spectradialog.ui @@ -80,6 +80,9 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + -4000.000000000000000 + 4000.000000000000000 From e37b4a8595ebd2d75402c891818b9527d563504c Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Thu, 19 Dec 2024 14:53:41 -0500 Subject: [PATCH 5/8] Fixup x-axis titles and add Wavelength (nm) for future use Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/spectra/spectradialog.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/avogadro/qtplugins/spectra/spectradialog.cpp b/avogadro/qtplugins/spectra/spectradialog.cpp index c37f1aa3da..e155eb564a 100644 --- a/avogadro/qtplugins/spectra/spectradialog.cpp +++ b/avogadro/qtplugins/spectra/spectradialog.cpp @@ -529,7 +529,8 @@ void SpectraDialog::updatePlot() transitions = fromMatrix(m_spectra["Electronic"].col(0)); intensities = fromMatrix(m_spectra["Electronic"].col(1)); windowName = tr("Electronic Spectra"); - xTitle = tr("eV"); + QString xWave = tr("Wavelength (nm)"); + xTitle = tr("Energy (eV)"); yTitle = tr("Intensity"); // save settings settings.setValue("spectra/electronicXMin", m_ui->xAxisMinimum->value()); @@ -548,7 +549,7 @@ void SpectraDialog::updatePlot() else // grab it from the CD data intensities = fromMatrix(m_spectra["CircularDichroism"].col(1)); windowName = tr("Circular Dichroism Spectra"); - xTitle = tr("eV)"); + xTitle = tr("Energy (eV)"); yTitle = tr("Intensity"); // save settings settings.setValue("spectra/CDXMin", m_ui->xAxisMinimum->value()); @@ -561,7 +562,7 @@ void SpectraDialog::updatePlot() transitions = fromMatrix(m_spectra["DensityOfStates"].col(0)); intensities = fromMatrix(m_spectra["DensityOfStates"].col(1)); windowName = tr("Density of States"); - xTitle = tr("eV"); + xTitle = tr("Energy (eV)"); yTitle = tr("Intensity"); // save settings settings.setValue("spectra/dosXMin", m_ui->xAxisMinimum->value()); From 9f512e8cd4afa073414f93b6a445b455f36a87c6 Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Fri, 20 Dec 2024 09:18:43 -0500 Subject: [PATCH 6/8] Update options to read from settings Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/spectra/spectradialog.cpp | 344 +++++++++++-------- avogadro/qtplugins/spectra/spectradialog.h | 4 +- avogadro/qtplugins/spectra/spectradialog.ui | 2 +- 3 files changed, 201 insertions(+), 149 deletions(-) diff --git a/avogadro/qtplugins/spectra/spectradialog.cpp b/avogadro/qtplugins/spectra/spectradialog.cpp index e155eb564a..9718944227 100644 --- a/avogadro/qtplugins/spectra/spectradialog.cpp +++ b/avogadro/qtplugins/spectra/spectradialog.cpp @@ -190,7 +190,8 @@ void SpectraDialog::updateElementCombo() void SpectraDialog::changeSpectra() { - // TODO: change the scale and offset based on defaults and settings + // based on the current spectra type, update the options + // and prep the spectra for plotting QSettings settings; disconnectOptions(); @@ -199,52 +200,83 @@ void SpectraDialog::changeSpectra() SpectraType type = static_cast(m_ui->combo_spectra->currentData().toInt()); + // only show for NMR m_ui->elementCombo->hide(); + // todo: some spectra might want to swtich units + + m_transitions.clear(); + m_intensities.clear(); switch (type) { case SpectraType::Infrared: - m_ui->scaleSpinBox->setValue(1.0); - m_ui->offsetSpinBox->setValue(0.0); - m_ui->xAxisMinimum->setValue(4000.0); - m_ui->xAxisMaximum->setValue(0.0); - m_ui->peakWidth->setValue(30.0); + m_transitions = fromMatrix(m_spectra["IR"].col(0)); + m_intensities = fromMatrix(m_spectra["IR"].col(1)); + + settings.beginGroup("spectra/ir"); + m_ui->scaleSpinBox->setValue(settings.value("scale", 1.0).toDouble()); + m_ui->offsetSpinBox->setValue(settings.value("offset", 0.0).toDouble()); + m_ui->xAxisMinimum->setValue(settings.value("xmin", 4000.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", 400.0).toDouble()); + m_ui->peakWidth->setValue(settings.value("fwhm", 30.0).toDouble()); + settings.endGroup(); break; case SpectraType::Raman: - m_ui->scaleSpinBox->setValue(1.0); - m_ui->offsetSpinBox->setValue(0.0); - m_ui->xAxisMinimum->setValue(0.0); - m_ui->xAxisMaximum->setValue(4000.0); - m_ui->peakWidth->setValue(30.0); + m_transitions = fromMatrix(m_spectra["Raman"].col(0)); + m_intensities = fromMatrix(m_spectra["Raman"].col(1)); + + settings.beginGroup("spectra/raman"); + m_ui->scaleSpinBox->setValue(settings.value("scale", 1.0).toDouble()); + m_ui->offsetSpinBox->setValue(settings.value("offset", 0.0).toDouble()); + m_ui->xAxisMinimum->setValue(settings.value("xmin", 0.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", 4000.0).toDouble()); + m_ui->peakWidth->setValue(settings.value("fwhm", 30.0).toDouble()); + settings.endGroup(); break; case SpectraType::NMR: - m_ui->scaleSpinBox->setValue(1.0); - m_ui->offsetSpinBox->setValue(0.0); - // todo: these should be per element - m_ui->peakWidth->setValue(0.1); + // settings handled per-element below m_ui->elementCombo->show(); break; case SpectraType::Electronic: - m_ui->scaleSpinBox->setValue(1.0); - m_ui->offsetSpinBox->setValue(0.0); + m_transitions = fromMatrix(m_spectra["Electronic"].col(0)); + m_intensities = fromMatrix(m_spectra["Electronic"].col(1)); + + settings.beginGroup("spectra/electronic"); + m_ui->scaleSpinBox->setValue(settings.value("scale", 1.0).toDouble()); + m_ui->offsetSpinBox->setValue(settings.value("offset", 0.0).toDouble()); // in eV - m_ui->xAxisMinimum->setValue(5.0); - m_ui->xAxisMaximum->setValue(1.0); - m_ui->peakWidth->setValue(0.1); + m_ui->xAxisMinimum->setValue(settings.value("xmin", 5.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", 1.0).toDouble()); + m_ui->peakWidth->setValue(settings.value("fwhm", 0.1).toDouble()); + settings.endGroup(); break; case SpectraType::CircularDichroism: - m_ui->scaleSpinBox->setValue(1.0); - m_ui->offsetSpinBox->setValue(0.0); - // default to eV - m_ui->xAxisMinimum->setValue(5.0); - m_ui->xAxisMaximum->setValue(1.0); - m_ui->peakWidth->setValue(0.1); + m_transitions = fromMatrix(m_spectra["Electronic"].col(0)); + // check if electronic has a third column + if (m_spectra["Electronic"].cols() > 2) + m_intensities = fromMatrix(m_spectra["Electronic"].col(2)); + else // grab it from the CD data + m_intensities = fromMatrix(m_spectra["CircularDichroism"].col(1)); + + settings.beginGroup("spectra/cd"); + m_ui->scaleSpinBox->setValue(settings.value("scale", 1.0).toDouble()); + m_ui->offsetSpinBox->setValue(settings.value("offset", 0.0).toDouble()); + // default to eV units + m_ui->xAxisMinimum->setValue(settings.value("xmin", 5.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", 1.0).toDouble()); + m_ui->peakWidth->setValue(settings.value("fwhm", 0.1).toDouble()); + settings.endGroup(); break; case SpectraType::DensityOfStates: - m_ui->scaleSpinBox->setValue(1.0); - m_ui->offsetSpinBox->setValue(0.0); - m_ui->xAxisMinimum->setValue(-50.0); - m_ui->xAxisMaximum->setValue(50.0); - m_ui->peakWidth->setValue(0.1); + m_transitions = fromMatrix(m_spectra["DensityOfStates"].col(0)); + m_intensities = fromMatrix(m_spectra["DensityOfStates"].col(1)); + + settings.beginGroup("spectra/dos"); + m_ui->scaleSpinBox->setValue(settings.value("scale", 1.0).toDouble()); + m_ui->offsetSpinBox->setValue(settings.value("offset", 0.0).toDouble()); + m_ui->xAxisMinimum->setValue(settings.value("xmin", -50.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", 50.0).toDouble()); + m_ui->peakWidth->setValue(settings.value("fwhm", 0.1).toDouble()); + settings.endGroup(); break; } @@ -253,76 +285,115 @@ void SpectraDialog::changeSpectra() // get the element int element = m_ui->elementCombo->currentData().toInt(); - qDebug() << " NMR element: " << element; + settings.beginGroup(QString("spectra/nmr/%1").arg(element)); + m_ui->scaleSpinBox->setValue(settings.value("scale", 1.0).toDouble()); + m_ui->peakWidth->setValue(settings.value("fwhm", 0.1).toDouble()); // tweak the default axis range // based on https://imserc.northwestern.edu/guide/eNMR/chem/NMRnuclei.html + // offsets are approximate from a few calculations + // .. to at least provide a starting point switch (element) { case 1: // 1H - m_ui->xAxisMinimum->setValue(12.0); - m_ui->xAxisMaximum->setValue(0.0); + m_ui->xAxisMinimum->setValue(settings.value("xmin", 12.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", 0.0).toDouble()); + m_ui->offsetSpinBox->setValue( + settings.value("offset", 31.876).toDouble()); break; case 3: // 7Li - m_ui->xAxisMinimum->setValue(-16.0); - m_ui->xAxisMaximum->setValue(11.0); + m_ui->xAxisMinimum->setValue(settings.value("xmin", -16.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", 11.0).toDouble()); + // TODO: offset + m_ui->offsetSpinBox->setValue(settings.value("offset", 0.0).toDouble()); break; case 5: // 11B - m_ui->xAxisMinimum->setValue(100.0); - m_ui->xAxisMaximum->setValue(-120.0); + m_ui->xAxisMinimum->setValue(settings.value("xmin", 100.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", -120.0).toDouble()); + // TODO: offset + m_ui->offsetSpinBox->setValue(settings.value("offset", 0.0).toDouble()); break; case 6: // 13C - m_ui->xAxisMinimum->setValue(200.0); - m_ui->xAxisMaximum->setValue(0.0); + m_ui->xAxisMinimum->setValue(settings.value("xmin", 200.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", 0.0).toDouble()); + m_ui->offsetSpinBox->setValue( + settings.value("offset", 192.038).toDouble()); break; case 7: // 15N - m_ui->xAxisMinimum->setValue(800.0); - m_ui->xAxisMaximum->setValue(0.0); + m_ui->xAxisMinimum->setValue(settings.value("xmin", 800.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", 0.0).toDouble()); + m_ui->offsetSpinBox->setValue( + settings.value("offset", -106.738).toDouble()); break; case 8: // 17O - m_ui->xAxisMinimum->setValue(1600.0); - m_ui->xAxisMaximum->setValue(-50.0); + m_ui->xAxisMinimum->setValue(settings.value("xmin", 1600.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", -50.0).toDouble()); + // TODO: offset + m_ui->offsetSpinBox->setValue(settings.value("offset", 0.0).toDouble()); break; case 9: // 19F - m_ui->xAxisMinimum->setValue(60.0); - m_ui->xAxisMaximum->setValue(-300.0); + m_ui->xAxisMinimum->setValue(settings.value("xmin", 60.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", -300.0).toDouble()); + m_ui->offsetSpinBox->setValue( + settings.value("offset", 206.735).toDouble()); break; case 14: // 29Si - m_ui->xAxisMinimum->setValue(50.0); - m_ui->xAxisMaximum->setValue(-200.0); + m_ui->xAxisMinimum->setValue(settings.value("xmin", 50.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", -200.0).toDouble()); + m_ui->offsetSpinBox->setValue( + settings.value("offset", 400.876).toDouble()); break; case 15: // 31P - m_ui->xAxisMinimum->setValue(250.0); - m_ui->xAxisMaximum->setValue(-250.0); + m_ui->xAxisMinimum->setValue(settings.value("xmin", 250.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", -250.0).toDouble()); + // TODO: offset + m_ui->offsetSpinBox->setValue(settings.value("offset", 0.0).toDouble()); break; default: - m_ui->xAxisMinimum->setValue(100.0); - m_ui->xAxisMaximum->setValue(-100.0); + m_ui->xAxisMinimum->setValue(settings.value("xmax", 100.0).toDouble()); + m_ui->xAxisMaximum->setValue(settings.value("xmax", -100.0).toDouble()); + m_ui->offsetSpinBox->setValue(settings.value("offset", 0.0).toDouble()); break; } + settings.endGroup(); // the default NMR data has all the atoms in it, // so we need to loop through m_elements to filter MatrixX nmr = m_spectra["NMR"]; - qDebug() << " NMR size: " << nmr.rows() << "x" << nmr.cols(); - qDebug() << " m_elements size: " << m_elements.size(); - - std::vector nmrPeaks; for (int i = 0; i < m_elements.size(); ++i) { if (m_elements[i] == element) { - nmrPeaks[i] = nmr(i, 0); + m_transitions.push_back(nmr(i, 0)); } } - MatrixX newNMR(nmrPeaks.size(), 2); - for (int i = 0; i < nmrPeaks.size(); ++i) { - newNMR(i, 0) = nmrPeaks[i]; - newNMR(i, 1) = 1.0; - } + // fill the intensities with 1.0 + m_intensities.resize(m_transitions.size(), 1.0); + } + // other spectra transitions and intensities are already set - m_currentSpectra = newNMR; - } else { - m_currentSpectra = - m_spectra[m_ui->combo_spectra->currentText().toStdString()]; + // update the data table + double maxIntensity = 0.0; + m_ui->dataTable->setRowCount(m_transitions.size()); + m_ui->dataTable->setColumnCount(2); + for (auto i = 0; i < m_transitions.size(); ++i) { + // frequency or energy + QTableWidgetItem* item = + new QTableWidgetItem(QString::number(m_transitions[i], 'f', 4)); + m_ui->dataTable->setItem(i, 0, item); + // intensities + item = new QTableWidgetItem(QString::number(m_intensities[i], 'f', 4)); + m_ui->dataTable->setItem(i, 1, item); + + if (m_intensities[i] > maxIntensity) + maxIntensity = m_intensities[i]; + } + + // update the spin boxes + m_ui->yAxisMaximum->setValue(maxIntensity); + m_ui->yAxisMinimum->setMinimum(0.0); + // if CD, set the minimum too + if (type == SpectraType::CircularDichroism) { + m_ui->yAxisMinimum->setMinimum(-maxIntensity * 2.0); + m_ui->yAxisMinimum->setValue(-maxIntensity); } updatePlot(); @@ -383,21 +454,24 @@ void SpectraDialog::writeSettings() const void SpectraDialog::readSettings() { QSettings settings; + settings.beginGroup("spectra"); // update the dialog with saved settings // font size - int fontSize = settings.value("spectra/fontSize", 12).toInt(); + int fontSize = settings.value("fontSize", 12).toInt(); m_ui->fontSizeCombo->setCurrentText(QString::number(fontSize)); // line width - float lineWidth = settings.value("spectra/lineWidth", 1.0).toFloat(); + float lineWidth = settings.value("lineWidth", 1.0).toFloat(); m_ui->lineWidthSpinBox->setValue(lineWidth); + + // TODO: other bits + settings.endGroup(); } void SpectraDialog::changeBackgroundColor() { QSettings settings; - QColor current = - settings.value("spectra/backgroundColor", white).value(); + QColor current = settings.value("backgroundColor", white).value(); QColor color = QColorDialog::getColor(current, this, tr("Select Background Color")); if (color.isValid() && color != current) { @@ -482,113 +556,97 @@ void SpectraDialog::updatePlot() QString windowName; QString xTitle; QString yTitle; + // TODO: switch units for electronic and CD + QString xWave = tr("Wavelength (nm)"); + bool transmission = false; // get the raw data from the spectra map switch (type) { case SpectraType::Infrared: - transitions = fromMatrix(m_spectra["IR"].col(0)); - intensities = fromMatrix(m_spectra["IR"].col(1)); windowName = tr("Vibrational Spectra"); xTitle = tr("Wavenumbers (cm⁻¹)"); yTitle = tr("Transmission"); transmission = true; - settings.setValue("spectra/irXMin", float(m_ui->xAxisMinimum->value())); - settings.setValue("spectra/irXMax", m_ui->xAxisMaximum->value()); - settings.setValue("spectra/irPeakWidth", float(m_ui->peakWidth->value())); - settings.setValue("spectra/irScale", m_ui->scaleSpinBox->value()); - settings.setValue("spectra/irOffset", m_ui->offsetSpinBox->value()); + settings.beginGroup("spectra/ir"); + settings.setValue("xmin", float(m_ui->xAxisMinimum->value())); + settings.setValue("xmax", m_ui->xAxisMaximum->value()); + settings.setValue("fwhm", float(m_ui->peakWidth->value())); + settings.setValue("scale", m_ui->scaleSpinBox->value()); + settings.setValue("offset", m_ui->offsetSpinBox->value()); + settings.endGroup(); break; case SpectraType::Raman: - transitions = fromMatrix(m_spectra["Raman"].col(0)); - intensities = fromMatrix(m_spectra["Raman"].col(1)); windowName = tr("Raman Spectra"); xTitle = tr("Wavenumbers (cm⁻¹)"); yTitle = tr("Intensity"); // save the plot settings - settings.setValue("spectra/ramanXMin", m_ui->xAxisMinimum->value()); - settings.setValue("spectra/ramanXMax", m_ui->xAxisMaximum->value()); - settings.setValue("spectra/ramanPeakWidth", m_ui->peakWidth->value()); - settings.setValue("spectra/ramanScale", m_ui->scaleSpinBox->value()); - settings.setValue("spectra/ramanOffset", m_ui->offsetSpinBox->value()); + settings.beginGroup("spectra/raman"); + settings.setValue("xmin", m_ui->xAxisMinimum->value()); + settings.setValue("xmax", m_ui->xAxisMaximum->value()); + settings.setValue("fwhm", m_ui->peakWidth->value()); + settings.setValue("scale", m_ui->scaleSpinBox->value()); + settings.setValue("offset", m_ui->offsetSpinBox->value()); + settings.endGroup(); break; case SpectraType::NMR: - transitions = fromMatrix(m_spectra["NMR"].col(0)); - intensities = fromMatrix(m_spectra["NMR"].col(1)); windowName = tr("NMR Spectra"); xTitle = tr("Chemical Shift (ppm)"); yTitle = tr("Intensity"); - // save the plot settings - settings.setValue("spectra/nmrXMin", m_ui->xAxisMinimum->value()); - settings.setValue("spectra/nmrXMax", m_ui->xAxisMaximum->value()); - settings.setValue("spectra/nmrPeakWidth", m_ui->peakWidth->value()); - settings.setValue("spectra/nmrScale", m_ui->scaleSpinBox->value()); - settings.setValue("spectra/nmrOffset", m_ui->offsetSpinBox->value()); + // save the plot settings on a per element basis + settings.beginGroup(QString("spectra/nmr/%1") + .arg(m_ui->elementCombo->currentData().toInt())); + settings.setValue("xmin", m_ui->xAxisMinimum->value()); + settings.setValue("xmax", m_ui->xAxisMaximum->value()); + settings.setValue("fwhm", m_ui->peakWidth->value()); + settings.setValue("scale", m_ui->scaleSpinBox->value()); + settings.setValue("offset", m_ui->offsetSpinBox->value()); + settings.endGroup(); break; case SpectraType::Electronic: - transitions = fromMatrix(m_spectra["Electronic"].col(0)); - intensities = fromMatrix(m_spectra["Electronic"].col(1)); windowName = tr("Electronic Spectra"); - QString xWave = tr("Wavelength (nm)"); xTitle = tr("Energy (eV)"); yTitle = tr("Intensity"); // save settings - settings.setValue("spectra/electronicXMin", m_ui->xAxisMinimum->value()); - settings.setValue("spectra/electronicXMax", m_ui->xAxisMaximum->value()); - settings.setValue("spectra/electronicPeakWidth", - m_ui->peakWidth->value()); - settings.setValue("spectra/electronicScale", m_ui->scaleSpinBox->value()); - settings.setValue("spectra/electronicOffset", - m_ui->offsetSpinBox->value()); + settings.beginGroup("spectra/electronic"); + settings.setValue("xmin", m_ui->xAxisMinimum->value()); + settings.setValue("xmax", m_ui->xAxisMaximum->value()); + settings.setValue("fwhm", m_ui->peakWidth->value()); + settings.setValue("scale", m_ui->scaleSpinBox->value()); + settings.setValue("offset", m_ui->offsetSpinBox->value()); + settings.endGroup(); break; case SpectraType::CircularDichroism: - transitions = fromMatrix(m_spectra["Electronic"].col(0)); - // check if electronic has a third column - if (m_spectra["Electronic"].cols() > 2) - intensities = fromMatrix(m_spectra["Electronic"].col(2)); - else // grab it from the CD data - intensities = fromMatrix(m_spectra["CircularDichroism"].col(1)); windowName = tr("Circular Dichroism Spectra"); xTitle = tr("Energy (eV)"); yTitle = tr("Intensity"); // save settings - settings.setValue("spectra/CDXMin", m_ui->xAxisMinimum->value()); - settings.setValue("spectra/CDXMax", m_ui->xAxisMaximum->value()); - settings.setValue("spectra/CDPeakWidth", m_ui->peakWidth->value()); - settings.setValue("spectra/CDScale", m_ui->scaleSpinBox->value()); - settings.setValue("spectra/CDOffset", m_ui->offsetSpinBox->value()); + settings.beginGroup("spectra/cd"); + settings.setValue("xmin", m_ui->xAxisMinimum->value()); + settings.setValue("xmax", m_ui->xAxisMaximum->value()); + settings.setValue("fwhm", m_ui->peakWidth->value()); + settings.setValue("scale", m_ui->scaleSpinBox->value()); + settings.setValue("offset", m_ui->offsetSpinBox->value()); + settings.endGroup(); break; case SpectraType::DensityOfStates: - transitions = fromMatrix(m_spectra["DensityOfStates"].col(0)); - intensities = fromMatrix(m_spectra["DensityOfStates"].col(1)); windowName = tr("Density of States"); xTitle = tr("Energy (eV)"); - yTitle = tr("Intensity"); + yTitle = tr("Density"); // save settings - settings.setValue("spectra/dosXMin", m_ui->xAxisMinimum->value()); - settings.setValue("spectra/dosXMax", m_ui->xAxisMaximum->value()); - settings.setValue("spectra/dosPeakWidth", m_ui->peakWidth->value()); - settings.setValue("spectra/dosScale", m_ui->scaleSpinBox->value()); - settings.setValue("spectra/dosOffset", m_ui->offsetSpinBox->value()); + settings.beginGroup("spectra/dos"); + settings.setValue("xmin", m_ui->xAxisMinimum->value()); + settings.setValue("xmax", m_ui->xAxisMaximum->value()); + settings.setValue("fwhm", m_ui->peakWidth->value()); + settings.setValue("scale", m_ui->scaleSpinBox->value()); + settings.setValue("offset", m_ui->offsetSpinBox->value()); + settings.endGroup(); break; } setWindowTitle(windowName); - // update the data table - m_ui->dataTable->setRowCount(transitions.size()); - m_ui->dataTable->setColumnCount(2); - for (auto i = 0; i < transitions.size(); ++i) { - // frequency or energy - QTableWidgetItem* item = - new QTableWidgetItem(QString::number(transitions[i], 'f', 4)); - m_ui->dataTable->setItem(i, 0, item); - // intensities - item = new QTableWidgetItem(QString::number(intensities[i], 'f', 4)); - m_ui->dataTable->setItem(i, 1, item); - } - double maxIntensity = 0.0f; - for (auto intensity : intensities) { + for (auto intensity : m_intensities) { if (intensity > maxIntensity) maxIntensity = intensity; } @@ -600,15 +658,6 @@ void SpectraDialog::updatePlot() if (maxIntensity < 1.0) maxIntensity = 1.0; - // update the spin boxes - m_ui->yAxisMaximum->setValue(maxIntensity); - m_ui->yAxisMinimum->setMinimum(0.0); - // if CD, set the minimum too - if (type == SpectraType::CircularDichroism) { - m_ui->yAxisMinimum->setMinimum(-maxIntensity * 2.0); - m_ui->yAxisMinimum->setValue(-maxIntensity); - } - // now compose the plot data float scale = m_ui->scaleSpinBox->value(); float offset = m_ui->offsetSpinBox->value(); @@ -633,11 +682,12 @@ void SpectraDialog::updatePlot() yStick.push_back(0.0f); // now we add up the intensity from any frequency - for (auto index = 0; index < transitions.size(); ++index) { - float freq = transitions[index]; - float peak = intensities[index]; + for (auto index = 0; index < m_transitions.size(); ++index) { + float freq = m_transitions[index]; + float peak = m_intensities[index]; float intensity = scaleAndBlur(xValue, freq, peak, scale, offset, fwhm); + // todo: find the closest point to the peak float stick = scaleAndBlur(xValue, freq, peak, scale, offset, stickWidth); yData.back() += intensity; diff --git a/avogadro/qtplugins/spectra/spectradialog.h b/avogadro/qtplugins/spectra/spectradialog.h index bea42c1d43..0e22496b5c 100644 --- a/avogadro/qtplugins/spectra/spectradialog.h +++ b/avogadro/qtplugins/spectra/spectradialog.h @@ -73,7 +73,9 @@ private slots: private: std::map m_spectra; std::vector m_elements; // for NMR - MatrixX m_currentSpectra; + // current spectra data + std::vector m_transitions; + std::vector m_intensities; QString m_currentSpectraType; Ui::SpectraDialog* m_ui; diff --git a/avogadro/qtplugins/spectra/spectradialog.ui b/avogadro/qtplugins/spectra/spectradialog.ui index fe80619479..9ca79bd1a3 100644 --- a/avogadro/qtplugins/spectra/spectradialog.ui +++ b/avogadro/qtplugins/spectra/spectradialog.ui @@ -163,7 +163,7 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - 1.000000000000000 + 0.010000000000000 From fb6e8a6de9da675fd2ca491eeaf1ca0f7680feff Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Fri, 20 Dec 2024 10:59:37 -0500 Subject: [PATCH 7/8] Add NMR plot Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/spectra/spectra.cpp | 4 ++++ avogadro/qtplugins/spectra/spectradialog.cpp | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/avogadro/qtplugins/spectra/spectra.cpp b/avogadro/qtplugins/spectra/spectra.cpp index 39b424334c..4389dd2ea1 100644 --- a/avogadro/qtplugins/spectra/spectra.cpp +++ b/avogadro/qtplugins/spectra/spectra.cpp @@ -106,6 +106,10 @@ void Spectra::openDialog() } gatherSpectra(); + // update the elements + auto elements = m_molecule->atomicNumbers(); + std::vector atomicNumbers(elements.begin(), elements.end()); + m_dialog->setElements(atomicNumbers); m_dialog->show(); } diff --git a/avogadro/qtplugins/spectra/spectradialog.cpp b/avogadro/qtplugins/spectra/spectradialog.cpp index 9718944227..b058109e0e 100644 --- a/avogadro/qtplugins/spectra/spectradialog.cpp +++ b/avogadro/qtplugins/spectra/spectradialog.cpp @@ -37,6 +37,7 @@ float scaleAndBlur(float x, float peak, float intensity, float scale = 1.0, // x is the absolute position, but we need to scale the peak position float scaled_peak = (peak - shift) / scale; + float delta = x - scaled_peak; float exponent = -(delta * delta) / (2 * sigma * sigma); float gaussian = exp(exponent); @@ -136,8 +137,13 @@ void SpectraDialog::updateElementCombo() SLOT(changeSpectra())); m_ui->elementCombo->clear(); + // go through the elements in atomic number order + // make a copy of the vector + std::vector elements = m_elements; + std::sort(elements.begin(), elements.end()); + // add the unique elements, with the element number as the data - for (auto& element : m_elements) { + for (auto& element : elements) { // check to see if it's already in the combo box bool found = false; for (int i = 0; i < m_ui->elementCombo->count(); ++i) { @@ -186,6 +192,7 @@ void SpectraDialog::updateElementCombo() // connect the element combo box connect(m_ui->elementCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSpectra())); + changeSpectra(); // default to 1H } void SpectraDialog::changeSpectra() @@ -661,6 +668,11 @@ void SpectraDialog::updatePlot() // now compose the plot data float scale = m_ui->scaleSpinBox->value(); float offset = m_ui->offsetSpinBox->value(); + // NMR offsets should be inverted + if (type == SpectraType::NMR) { + scale = -scale; + } + float fwhm = m_ui->peakWidth->value(); float xMin = m_ui->xAxisMinimum->value(); @@ -672,6 +684,8 @@ void SpectraDialog::updatePlot() float xScale = 1.0; if (type == SpectraType::Electronic || type == SpectraType::CircularDichroism) xScale = 1.0f / 0.05f; + else if (type == SpectraType::NMR) + xScale = 1.0f / 0.01f; float stickWidth = fwhm / (xScale * 30.0); From b89a7323b616a41c0fb9eba21b120e401b629c3a Mon Sep 17 00:00:00 2001 From: Geoff Hutchison Date: Fri, 20 Dec 2024 12:55:11 -0500 Subject: [PATCH 8/8] Fix the stick spectra by using "closest to" x-axis point Signed-off-by: Geoff Hutchison --- avogadro/qtplugins/spectra/spectradialog.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/avogadro/qtplugins/spectra/spectradialog.cpp b/avogadro/qtplugins/spectra/spectradialog.cpp index b058109e0e..0576273907 100644 --- a/avogadro/qtplugins/spectra/spectradialog.cpp +++ b/avogadro/qtplugins/spectra/spectradialog.cpp @@ -44,6 +44,19 @@ float scaleAndBlur(float x, float peak, float intensity, float scale = 1.0, return intensity * gaussian; } +float closestTo(float x, float peak, float intensity, float scale = 1.0, + float shift = 0.0, float xScale = 1.0) +{ + // return peak intensity if x is closer to the peak than another point + // scaled by scale and shifted by shift + float scaled_peak = (peak - shift) / scale; + float delta = x - scaled_peak; + // xScale is the reciprocal of the space between points + // (i.e., used to generate many points in the loop) + float peak_to_peak = 1.0 / xScale; + return (fabs(delta) < peak_to_peak / 2.0) ? intensity : 0.0; +} + std::vector fromMatrix(const MatrixX& matrix) { std::vector result; @@ -687,8 +700,7 @@ void SpectraDialog::updatePlot() else if (type == SpectraType::NMR) xScale = 1.0f / 0.01f; - float stickWidth = fwhm / (xScale * 30.0); - + // TODO: process an experimental spectrum via interpolation for (unsigned int x = round(start * xScale); x < round(end * xScale); ++x) { float xValue = static_cast(x) / xScale; xData.push_back(xValue); @@ -701,8 +713,7 @@ void SpectraDialog::updatePlot() float peak = m_intensities[index]; float intensity = scaleAndBlur(xValue, freq, peak, scale, offset, fwhm); - // todo: find the closest point to the peak - float stick = scaleAndBlur(xValue, freq, peak, scale, offset, stickWidth); + float stick = closestTo(xValue, freq, peak, scale, offset, xScale); yData.back() += intensity; yStick.back() += stick;