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 730c4484dc..0576273907 100644 --- a/avogadro/qtplugins/spectra/spectradialog.cpp +++ b/avogadro/qtplugins/spectra/spectradialog.cpp @@ -37,12 +37,26 @@ 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); 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; @@ -55,6 +69,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); @@ -63,6 +85,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 +101,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,103 +124,300 @@ SpectraDialog::SpectraDialog(QWidget* parent) SLOT(updatePlot())); connect(m_ui->peakWidth, SIGNAL(valueChanged(double)), this, SLOT(updatePlot())); +} - readSettings(); +void SpectraDialog::disconnectOptions() +{ + // 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())); } -SpectraDialog::~SpectraDialog() +void SpectraDialog::updateElementCombo() { - writeSettings(); + // update the element combo box + disconnect(m_ui->elementCombo, SIGNAL(currentIndexChanged(int)), this, + 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 : 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())); + changeSpectra(); // default to 1H } 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(); + // what type of spectra are we plotting? 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->xAxisMinimum->setValue(0.0); - m_ui->xAxisMaximum->setValue(200.0); - 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); - 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; } - 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); + // a bunch of special work depending on the NMR element + if (type == SpectraType::NMR) { + // get the element + int element = m_ui->elementCombo->currentData().toInt(); + + 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(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(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(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(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(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(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(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(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(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(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; } - } - // 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); + 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"]; + + for (int i = 0; i < m_elements.size(); ++i) { + if (m_elements[i] == element) { + m_transitions.push_back(nmr(i, 0)); + } } - maxIntensity = maxIntensity * 1.25; + // fill the intensities with 1.0 + m_intensities.resize(m_transitions.size(), 1.0); } - // if transmission for IR, set the max intensity to 100 - if (type == SpectraType::Infrared) - maxIntensity = 100.0; + // other spectra transitions and intensities are already set - if (maxIntensity < 1.0) - maxIntensity = 1.0; + // 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 box + // 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(); + connectOptions(); } void SpectraDialog::setSpectra(const std::map& spectra) @@ -193,9 +425,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 +458,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 @@ -236,21 +474,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) { @@ -319,6 +560,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 @@ -332,123 +576,144 @@ 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"); - xTitle = tr("eV"); + 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)); - intensities = fromMatrix(m_spectra["Electronic"].col(2)); 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()); - 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("eV"); - yTitle = tr("Intensity"); + xTitle = tr("Energy (eV)"); + 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); double maxIntensity = 0.0f; - for (auto intensity : intensities) { + for (auto intensity : m_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; + // 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(); 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)); - - for (unsigned int x = start; x < end; ++x) { - float xValue = static_cast(x); + 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; + else if (type == SpectraType::NMR) + xScale = 1.0f / 0.01f; + + // 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); yData.push_back(0.0f); 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); - float stick = scaleAndBlur(xValue, freq, peak, scale, offset, 1.0); + float stick = closestTo(xValue, freq, peak, scale, offset, xScale); yData.back() += intensity; yStick.back() += stick; @@ -492,6 +757,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..0e22496b5c 100644 --- a/avogadro/qtplugins/spectra/spectradialog.h +++ b/avogadro/qtplugins/spectra/spectradialog.h @@ -45,9 +45,17 @@ 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(); + void disconnectOptions(); + void connectOptions(); + private slots: void changeBackgroundColor(); void changeForegroundColor(); @@ -57,14 +65,19 @@ private slots: void changeLineWidth(); void changeSpectra(); + void updateElementCombo(); void updatePlot(); void toggleOptions(); private: std::map m_spectra; + std::vector m_elements; // for NMR + // current spectra data + std::vector m_transitions; + std::vector m_intensities; - 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 2d0574af20..9ca79bd1a3 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 - - - - + @@ -80,6 +44,9 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + -4000.000000000000000 + 4000.000000000000000 @@ -196,7 +163,7 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - 1.000000000000000 + 0.010000000000000 @@ -217,6 +184,20 @@ + + + + Units: + + + + + + + false + + + @@ -461,7 +442,50 @@ - + + + + + 0 + 0 + + + + + + + + Export Data + + + + + + + + 0 + 0 + + + + &Close + + + + + + + + 0 + 0 + + + + &Options… + + + + @@ -517,30 +541,7 @@ - - - - - 0 - 0 - - - - &Options… - - - - - - - - 0 - 0 - - - - - + @@ -572,6 +573,36 @@ Scroll wheel: Zoom to cursor + + + + false + + + + 0 + 0 + + + + &Load data... + + + + + + + + ¹H + + + + + ¹³C + + + + 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);