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