1 /******************************************************************************
2 
3   This source file is part of the Avogadro project.
4 
5   Copyright 2018 Kitware, Inc.
6 
7   This source code is released under the New BSD License, (the "License").
8 
9   Unless required by applicable law or agreed to in writing, software
10   distributed under the License is distributed on an "AS IS" BASIS,
11   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   See the License for the specific language governing permissions and
13   limitations under the License.
14 
15 ******************************************************************************/
16 
17 #include <QAction>
18 #include <QDialog>
19 #include <QMessageBox>
20 #include <QString>
21 
22 #include <avogadro/core/crystaltools.h>
23 #include <avogadro/core/unitcell.h>
24 #include <avogadro/qtgui/molecule.h>
25 #include <avogadro/vtk/vtkplot.h>
26 
27 #include "pdfoptionsdialog.h"
28 #include "plotpdf.h"
29 
30 using Avogadro::Core::CrystalTools;
31 using Avogadro::Core::UnitCell;
32 using Avogadro::QtGui::Molecule;
33 
34 using std::map;
35 
36 namespace Avogadro {
37 namespace QtPlugins {
38 
39 using Core::Array;
40 
PlotPdf(QObject * parent_)41 PlotPdf::PlotPdf(QObject* parent_)
42   : Avogadro::QtGui::ExtensionPlugin(parent_)
43   , m_actions(QList<QAction*>())
44   , m_molecule(nullptr)
45   , m_pdfOptionsDialog(new PdfOptionsDialog(qobject_cast<QWidget*>(parent())))
46   , m_displayDialogAction(new QAction(this))
47 {
48   m_displayDialogAction->setText(tr("Plot Pair Distribution Function..."));
49   connect(m_displayDialogAction.data(), &QAction::triggered, this,
50           &PlotPdf::displayDialog);
51   m_actions.push_back(m_displayDialogAction.data());
52   m_displayDialogAction->setProperty("menu priority", 70);
53 
54   updateActions();
55 }
56 
57 PlotPdf::~PlotPdf() = default;
58 
actions() const59 QList<QAction*> PlotPdf::actions() const
60 {
61   return m_actions;
62 }
63 
menuPath(QAction *) const64 QStringList PlotPdf::menuPath(QAction*) const
65 {
66   return QStringList() << tr("&Crystal");
67 }
68 
setMolecule(QtGui::Molecule * mol)69 void PlotPdf::setMolecule(QtGui::Molecule* mol)
70 {
71   if (m_molecule == mol)
72     return;
73 
74   if (m_molecule)
75     m_molecule->disconnect(this);
76 
77   m_molecule = mol;
78 
79   if (m_molecule)
80     connect(m_molecule, SIGNAL(changed(uint)), SLOT(moleculeChanged(uint)));
81 
82   updateActions();
83 }
84 
moleculeChanged(unsigned int c)85 void PlotPdf::moleculeChanged(unsigned int c)
86 {
87   Q_ASSERT(m_molecule == qobject_cast<Molecule*>(sender()));
88 
89   Molecule::MoleculeChanges changes = static_cast<Molecule::MoleculeChanges>(c);
90 
91   if (changes & Molecule::UnitCell) {
92     if (changes & Molecule::Added || changes & Molecule::Removed)
93       updateActions();
94   }
95 }
96 
updateActions()97 void PlotPdf::updateActions()
98 {
99   // Disable everything for nullptr molecules.
100   if (!m_molecule) {
101     foreach (QAction* action, m_actions)
102       action->setEnabled(false);
103     return;
104   }
105 
106   // Only display the actions if there is a unit cell
107   if (m_molecule->unitCell()) {
108     foreach (QAction* action, m_actions)
109       action->setEnabled(true);
110   } else {
111     foreach (QAction* action, m_actions)
112       action->setEnabled(false);
113   }
114 }
115 
displayDialog()116 void PlotPdf::displayDialog()
117 {
118   // Do nothing if the user cancels
119   if (m_pdfOptionsDialog->exec() != QDialog::Accepted)
120     return;
121 
122   // Otherwise, fetch the options and perform the run
123   double maxRadius = m_pdfOptionsDialog->maxRadius();
124   double step = m_pdfOptionsDialog->step();
125   // size_t numpoints = m_pdfOptionsDialog->numDataPoints();
126   // double max2theta = m_pdfOptionsDialog->max2Theta();
127 
128   PdfData results;
129   QString err;
130   if (!generatePdfPattern(*m_molecule, results, err, maxRadius, step)) {
131     QMessageBox::critical(qobject_cast<QWidget*>(parent()),
132                           tr("Failed to generate PDF pattern"),
133                           tr("Error message: ") + err);
134     return;
135   }
136 
137   // Now generate a plot with the data
138   std::vector<double> xData;
139   std::vector<double> yData;
140   for (const auto& item : results) {
141     xData.push_back(item.first);
142     yData.push_back(item.second);
143   }
144   std::vector<std::vector<double>> data{ xData, yData };
145 
146   std::vector<std::string> lineLabels{ "PdfData" };
147 
148   std::array<double, 4> color = { 255, 0, 0, 255 };
149   std::vector<std::array<double, 4>> lineColors{ color };
150 
151   const char* xTitle = "r (Å)";
152   const char* yTitle = "g(r)";
153   const char* windowName = "Pair Distribution Function";
154 
155   if (!m_plot)
156     m_plot.reset(new VTK::VtkPlot);
157 
158   m_plot->setData(data);
159   m_plot->setWindowName(windowName);
160   m_plot->setXTitle(xTitle);
161   m_plot->setYTitle(yTitle);
162   m_plot->setLineLabels(lineLabels);
163   m_plot->setLineColors(lineColors);
164   m_plot->show();
165 }
166 
generatePdfPattern(QtGui::Molecule & mol,PdfData & results,QString & err,double maxRadius,double step)167 bool PlotPdf::generatePdfPattern(QtGui::Molecule& mol, PdfData& results,
168                                  QString& err, double maxRadius, double step)
169 {
170   Array<Vector3> refAtomCoords = mol.atomPositions3d();
171 
172   size_t i, j;
173 
174   UnitCell* uc = mol.unitCell();
175   if (!uc) {
176     err = "No unit cell found.";
177     return false;
178   }
179 
180   size_t a = static_cast<size_t>(maxRadius / uc->a()) + 1;
181   size_t b = static_cast<size_t>(maxRadius / uc->b()) + 1;
182   size_t c = static_cast<size_t>(maxRadius / uc->c()) + 1;
183 
184   Vector3 disp = a * uc->aVector() + b * uc->bVector() + c * uc->cVector();
185   for (i = 0; i < refAtomCoords.size(); ++i) {
186     refAtomCoords[i] += disp;
187   }
188 
189   Molecule newMolecule = mol;
190   CrystalTools::buildSupercell(newMolecule, 2 * a + 1, 2 * b + 1, 2 * c + 1);
191 
192   Array<Vector3> newAtomCoords = newMolecule.atomPositions3d();
193 
194   map<size_t, size_t> pdfCount;
195   double dist, rStep = step;
196   size_t k, binIdx;
197 
198   for (i = 0; i < refAtomCoords.size(); ++i) {
199     for (j = 0; j < newAtomCoords.size(); ++j) {
200       dist = (refAtomCoords.at(i) - newAtomCoords.at(j)).norm();
201       binIdx = static_cast<size_t>(dist / rStep);
202       if (pdfCount.find(binIdx) == pdfCount.end()) {
203         pdfCount.insert(std::make_pair(binIdx, 1));
204       } else {
205         pdfCount[binIdx]++;
206       }
207     }
208   }
209 
210   for (k = 0; k < static_cast<size_t>(maxRadius / rStep); k++) {
211     if (pdfCount.find(k) == pdfCount.end()) {
212       results.push_back(std::make_pair(k * rStep, 0.0));
213     } else {
214       results.push_back(std::make_pair(
215         k * rStep, pdfCount[k] * newMolecule.unitCell()->volume() /
216                      (4 * M_PI * pow(k * rStep, 2) * rStep *
217                       refAtomCoords.size() * newAtomCoords.size())));
218     }
219   }
220 
221   return true;
222 }
223 
224 } // namespace QtPlugins
225 } // namespace Avogadro
226