1 /******************************************************************************
2
3 This source file is part of the Avogadro project.
4
5 Copyright 2012 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 "gamessinputdialog.h"
18 #include "gamesshighlighter.h"
19
20 #include <avogadro/core/atom.h>
21 #include <avogadro/core/bond.h>
22 #include <avogadro/core/elements.h>
23
24 #include <avogadro/molequeue/molequeuedialog.h>
25 #include <avogadro/molequeue/molequeuemanager.h>
26 #include <avogadro/qtgui/molecule.h>
27
28 #include <avogadro/molequeue/client/jobobject.h>
29 #include <qjsonarray.h>
30 #include <qjsonobject.h>
31 #include <qjsonvalue.h>
32
33 #include <QtWidgets/QFileDialog>
34 #include <QtWidgets/QMessageBox>
35 #include <QtWidgets/QProgressDialog>
36
37 #include <QtCore/QFile>
38 #include <QtCore/QSettings>
39 #include <QtCore/QString>
40 #include <QtCore/QTimer>
41
42 using Avogadro::MoleQueue::MoleQueueDialog;
43 using Avogadro::MoleQueue::MoleQueueManager;
44 using Avogadro::MoleQueue::JobObject;
45
46 namespace Avogadro {
47 namespace QtPlugins {
48
49 enum CalculateOption
50 {
51 CalculateSinglePoint = 0,
52 CalculateEquilibriumGeometry,
53 CalculateTransitionState,
54 CalculateFrequencies,
55
56 CalculateCount
57 };
58
59 enum TheoryOption
60 {
61 TheoryAM1 = 0,
62 TheoryPM3,
63 TheoryRHF,
64 TheoryB3LYP,
65 TheoryMP2,
66 TheoryCCSDT,
67
68 TheoryCount
69 };
70
71 enum BasisOption
72 {
73 BasisSTO3G = 0,
74 BasisMINI,
75 Basis321G,
76 Basis631Gd,
77 Basis631Gdp,
78 Basis631PlusGdp,
79 Basis631PlusG2dp,
80 Basis6311PlusPlusG2dp,
81 BasisCorePotential,
82
83 BasisCount
84 };
85
86 enum StateOption
87 {
88 StateGas = 0,
89 StateWater,
90
91 StateCount
92 };
93
94 enum MultiplicityOption
95 {
96 MultiplicitySinglet = 0,
97 MultiplicityDoublet,
98 MultiplicityTriplet,
99
100 MultiplicityCount
101 };
102
103 enum ChargeOption
104 {
105 ChargeDication = 0,
106 ChargeCation,
107 ChargeNeutral,
108 ChargeAnion,
109 ChargeDianion,
110
111 ChargeCount
112 };
113
GamessInputDialog(QWidget * parent_,Qt::WindowFlags f)114 GamessInputDialog::GamessInputDialog(QWidget* parent_, Qt::WindowFlags f)
115 : QDialog(parent_, f), m_molecule(nullptr), m_highlighter(nullptr),
116 m_updatePending(false)
117 {
118 ui.setupUi(this);
119 m_highlighter = new GamessHighlighter(ui.previewText->document());
120
121 buildOptions();
122
123 connectBasic();
124 connectPreview();
125 connectButtons();
126
127 setBasicDefaults();
128
129 updatePreviewText();
130 }
131
~GamessInputDialog()132 GamessInputDialog::~GamessInputDialog()
133 {
134 }
135
setMolecule(QtGui::Molecule * mol)136 void GamessInputDialog::setMolecule(QtGui::Molecule* mol)
137 {
138 if (mol == m_molecule)
139 return;
140
141 if (m_molecule)
142 m_molecule->disconnect(this);
143
144 m_molecule = mol;
145
146 connect(mol, SIGNAL(changed(unsigned int)), SLOT(updatePreviewText()));
147 connect(mol, SIGNAL(changed(unsigned int)), SLOT(updateTitlePlaceholder()));
148
149 updateTitlePlaceholder();
150 updatePreviewText();
151 }
152
showEvent(QShowEvent * e)153 void GamessInputDialog::showEvent(QShowEvent* e)
154 {
155 QWidget::showEvent(e);
156
157 // Update the preview text if an update was requested while hidden. Use a
158 // single shot to allow the dialog to show before popping up any warnings.
159 if (m_updatePending)
160 QTimer::singleShot(0, this, SLOT(updatePreviewText()));
161 }
162
connectBasic()163 void GamessInputDialog::connectBasic()
164 {
165 connect(ui.titleEdit, SIGNAL(textChanged(QString)), this,
166 SLOT(updatePreviewText()));
167 connect(ui.calculateCombo, SIGNAL(currentIndexChanged(int)), this,
168 SLOT(updatePreviewText()));
169 connect(ui.calculateCombo, SIGNAL(currentIndexChanged(int)), this,
170 SLOT(updateTitlePlaceholder()));
171 connect(ui.theoryCombo, SIGNAL(currentIndexChanged(int)), this,
172 SLOT(updatePreviewText()));
173 connect(ui.theoryCombo, SIGNAL(currentIndexChanged(int)), this,
174 SLOT(updateTitlePlaceholder()));
175 connect(ui.basisCombo, SIGNAL(currentIndexChanged(int)), this,
176 SLOT(updatePreviewText()));
177 connect(ui.basisCombo, SIGNAL(currentIndexChanged(int)), this,
178 SLOT(updateTitlePlaceholder()));
179 connect(ui.stateCombo, SIGNAL(currentIndexChanged(int)), this,
180 SLOT(updatePreviewText()));
181 connect(ui.multiplicityCombo, SIGNAL(currentIndexChanged(int)), this,
182 SLOT(updatePreviewText()));
183 connect(ui.chargeCombo, SIGNAL(currentIndexChanged(int)), this,
184 SLOT(updatePreviewText()));
185 }
186
connectPreview()187 void GamessInputDialog::connectPreview()
188 {
189 }
190
connectButtons()191 void GamessInputDialog::connectButtons()
192 {
193 connect(ui.resetAllButton, SIGNAL(clicked()), SLOT(resetClicked()));
194 connect(ui.defaultsButton, SIGNAL(clicked()), SLOT(defaultsClicked()));
195 connect(ui.generateButton, SIGNAL(clicked()), SLOT(generateClicked()));
196 connect(ui.computeButton, SIGNAL(clicked()), SLOT(computeClicked()));
197 connect(ui.closeButton, SIGNAL(clicked()), SLOT(close()));
198 }
199
buildOptions()200 void GamessInputDialog::buildOptions()
201 {
202 buildCalculateOptions();
203 buildTheoryOptions();
204 buildBasisOptions();
205 buildStateOptions();
206 buildMultiplicityOptions();
207 buildChargeOptions();
208 }
209
updateOptionCache()210 void GamessInputDialog::updateOptionCache()
211 {
212 m_optionCache.clear();
213 m_optionCache.insert(ui.calculateCombo, ui.calculateCombo->currentIndex());
214 m_optionCache.insert(ui.theoryCombo, ui.theoryCombo->currentIndex());
215 m_optionCache.insert(ui.basisCombo, ui.basisCombo->currentIndex());
216 m_optionCache.insert(ui.stateCombo, ui.stateCombo->currentIndex());
217 m_optionCache.insert(ui.multiplicityCombo,
218 ui.multiplicityCombo->currentIndex());
219 m_optionCache.insert(ui.chargeCombo, ui.chargeCombo->currentIndex());
220 }
221
restoreOptionCache()222 void GamessInputDialog::restoreOptionCache()
223 {
224 foreach (QComboBox* combo, m_optionCache.keys()) {
225 combo->blockSignals(true);
226 combo->setCurrentIndex(m_optionCache.value(combo, 0));
227 combo->blockSignals(false);
228 }
229 }
230
buildCalculateOptions()231 void GamessInputDialog::buildCalculateOptions()
232 {
233 for (int i = 0; i < static_cast<int>(CalculateCount); ++i) {
234 QString text = "";
235 switch (static_cast<CalculateOption>(i)) {
236 case CalculateSinglePoint:
237 text = tr("Single Point");
238 break;
239 case CalculateEquilibriumGeometry:
240 text = tr("Equilibrium Geometry");
241 break;
242 case CalculateTransitionState:
243 text = tr("Transition State");
244 break;
245 case CalculateFrequencies:
246 text = tr("Frequencies");
247 break;
248 default:
249 break;
250 }
251 ui.calculateCombo->addItem(text);
252 }
253 }
254
buildTheoryOptions()255 void GamessInputDialog::buildTheoryOptions()
256 {
257 for (int i = 0; i < static_cast<int>(TheoryCount); ++i) {
258 QString text = "";
259 switch (static_cast<TheoryOption>(i)) {
260 case TheoryAM1:
261 text = "AM1";
262 break;
263 case TheoryPM3:
264 text = "PM3";
265 break;
266 case TheoryRHF:
267 text = "RHF";
268 break;
269 case TheoryB3LYP:
270 text = "B3LYP";
271 break;
272 case TheoryMP2:
273 text = "MP2";
274 break;
275 case TheoryCCSDT:
276 text = "CCSD(T)";
277 break;
278 default:
279 break;
280 }
281 ui.theoryCombo->addItem(text);
282 }
283 }
284
buildBasisOptions()285 void GamessInputDialog::buildBasisOptions()
286 {
287 for (int i = 0; i < static_cast<int>(BasisCount); ++i) {
288 QString text = "";
289 switch (static_cast<BasisOption>(i)) {
290 case BasisSTO3G:
291 text = "STO-3G";
292 break;
293 case BasisMINI:
294 text = "MINI";
295 break;
296 case Basis321G:
297 text = "3-21 G";
298 break;
299 case Basis631Gd:
300 text = "6-31 G(d)";
301 break;
302 case Basis631Gdp:
303 text = "6-31 G(d,p)";
304 break;
305 case Basis631PlusGdp:
306 text = "6-31+G(d,p)";
307 break;
308 case Basis631PlusG2dp:
309 text = "6-31+G(2d,p)";
310 break;
311 case Basis6311PlusPlusG2dp:
312 text = "6-311++G(2d,p)";
313 break;
314 case BasisCorePotential:
315 text = tr("Core Potential");
316 break;
317 default:
318 break;
319 }
320 ui.basisCombo->addItem(text);
321 }
322 }
323
buildStateOptions()324 void GamessInputDialog::buildStateOptions()
325 {
326 for (int i = 0; i < static_cast<int>(StateCount); ++i) {
327 QString text = "";
328 switch (static_cast<StateOption>(i)) {
329 case StateGas:
330 text = tr("Gas");
331 break;
332 case StateWater:
333 text = tr("Water");
334 break;
335 default:
336 break;
337 }
338 ui.stateCombo->addItem(text);
339 }
340 }
341
buildMultiplicityOptions()342 void GamessInputDialog::buildMultiplicityOptions()
343 {
344 for (int i = 0; i < static_cast<int>(MultiplicityCount); ++i) {
345 QString text = "";
346 switch (static_cast<MultiplicityOption>(i)) {
347 case MultiplicitySinglet:
348 text = tr("Singlet");
349 break;
350 case MultiplicityDoublet:
351 text = tr("Doublet");
352 break;
353 case MultiplicityTriplet:
354 text = tr("Triplet");
355 break;
356 default:
357 break;
358 }
359 ui.multiplicityCombo->addItem(text);
360 }
361 }
362
buildChargeOptions()363 void GamessInputDialog::buildChargeOptions()
364 {
365 for (int i = 0; i < static_cast<int>(ChargeCount); ++i) {
366 QString text = "";
367 switch (static_cast<ChargeOption>(i)) {
368 case ChargeDication:
369 text = tr("Dication");
370 break;
371 case ChargeCation:
372 text = tr("Cation");
373 break;
374 case ChargeNeutral:
375 text = tr("Neutral");
376 break;
377 case ChargeAnion:
378 text = tr("Anion");
379 break;
380 case ChargeDianion:
381 text = tr("Dianion");
382 break;
383 default:
384 break;
385 }
386 ui.chargeCombo->addItem(text);
387 }
388 }
389
setBasicDefaults()390 void GamessInputDialog::setBasicDefaults()
391 {
392 ui.titleEdit->setText(QString());
393 ui.calculateCombo->setCurrentIndex(CalculateSinglePoint);
394 ui.theoryCombo->setCurrentIndex(TheoryB3LYP);
395 ui.basisCombo->setCurrentIndex(Basis321G);
396 ui.stateCombo->setCurrentIndex(StateGas);
397 ui.multiplicityCombo->setCurrentIndex(MultiplicitySinglet);
398 ui.chargeCombo->setCurrentIndex(ChargeNeutral);
399 }
400
generateJobTitle() const401 QString GamessInputDialog::generateJobTitle() const
402 {
403 QString calculation(ui.calculateCombo->currentText());
404 QString theory(ui.theoryCombo->currentText());
405 QString basis(ui.basisCombo->currentText());
406 QString formula(m_molecule ? QString::fromStdString(m_molecule->formula())
407 : tr("[no molecule]"));
408
409 // Merge theory/basis into theory
410 theory += "/" + basis;
411 theory.replace(QRegExp("\\s+"), "");
412
413 return QString("%1 | %2 | %3").arg(formula, calculation, theory);
414 }
415
updatePreviewText()416 void GamessInputDialog::updatePreviewText()
417 {
418 // If the dialog is not shown, delay the update in case we need to prompt the
419 // user to overwrite changes. Set the m_updatePending flag to true so we'll
420 // know to update in the show event.
421 if (!isVisible()) {
422 m_updatePending = true;
423 return;
424 }
425
426 m_updatePending = false;
427
428 // Has the preview text been modified?
429 if (ui.previewText->document()->isModified()) {
430 QString message = tr("The input file has been modified. "
431 "Would you like to overwrite your changes to reflect "
432 "the new geometry or job options?");
433 int response = QMessageBox::question(
434 this, tr("Overwrite modified input file?"), message,
435 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
436 if (static_cast<QMessageBox::StandardButton>(response) !=
437 QMessageBox::Yes) {
438 restoreOptionCache();
439 return;
440 }
441 }
442
443 // Gather options:
444 QString title(ui.titleEdit->text());
445 if (title.isEmpty())
446 title = generateJobTitle();
447
448 CalculateOption calculate(
449 static_cast<CalculateOption>(ui.calculateCombo->currentIndex()));
450 TheoryOption theory(
451 static_cast<TheoryOption>(ui.theoryCombo->currentIndex()));
452 BasisOption basis(static_cast<BasisOption>(ui.basisCombo->currentIndex()));
453 StateOption state(static_cast<StateOption>(ui.stateCombo->currentIndex()));
454 MultiplicityOption multiplicity(
455 static_cast<MultiplicityOption>(ui.multiplicityCombo->currentIndex()));
456 ChargeOption charge(
457 static_cast<ChargeOption>(ui.chargeCombo->currentIndex()));
458
459 // Disable basis selection for semiempirical methods.
460 ui.basisCombo->setEnabled(theory != TheoryAM1 && theory != TheoryPM3);
461
462 // Generate text.
463 // Variables:
464 QString runTyp;
465 QString scfTyp;
466 QString gBasis;
467 QString mult;
468 QString iCharg;
469
470 // Extra options for lines
471 QString extraBasis;
472 QString extraContrl;
473
474 // Optional lines
475 QString statPt;
476 QString force;
477 QString pcm;
478
479 switch (calculate) {
480 case CalculateSinglePoint:
481 runTyp = "ENERGY";
482 break;
483 case CalculateEquilibriumGeometry:
484 runTyp = "OPTIMIZE";
485 statPt = " $STATPT OPTTOL=0.0001 NSTEP=20 $END\n";
486 break;
487 case CalculateTransitionState:
488 runTyp = "SADPOINT";
489 statPt = " $STATPT OPTTOL=0.0001 NSTEP=20 $END\n";
490 break;
491 case CalculateFrequencies:
492 runTyp = "HESSIAN";
493 force = " $FORCE METHOD=ANALYTIC VIBANL=.TRUE. $END\n";
494 break;
495 default:
496 break;
497 }
498
499 switch (theory) {
500 case TheoryAM1:
501 gBasis = "AM1";
502 break;
503 case TheoryPM3:
504 gBasis = "PM3";
505 break;
506 case TheoryRHF:
507 break;
508 case TheoryB3LYP:
509 extraContrl += " DFTTYP=B3LYP";
510 break;
511 case TheoryMP2:
512 extraContrl += " MPLEVL=2";
513 break;
514 case TheoryCCSDT:
515 extraContrl += " CCTYP=CCSD(T)";
516 break;
517 default:
518 break;
519 }
520
521 if (theory != TheoryAM1 && theory != TheoryPM3) {
522 switch (basis) {
523 case BasisSTO3G:
524 gBasis = "STO";
525 extraBasis += " NGAUSS=3";
526 break;
527 case BasisMINI:
528 gBasis = "MINI";
529 break;
530 case Basis321G:
531 gBasis = "N21";
532 extraBasis += " NGAUSS=3";
533 break;
534 case Basis631Gd:
535 gBasis = "N31";
536 extraBasis += " NGAUSS=6 NDFUNC=1";
537 break;
538 case Basis631Gdp:
539 gBasis = "N31";
540 extraBasis += " NGAUSS=6 NDFUNC=1 NPFUNC=1";
541 break;
542 case Basis631PlusGdp:
543 gBasis = "N31";
544 extraBasis += " NGAUSS=6 NDFUNC=1 NPFUNC=1 DIFFSP=.TRUE.";
545 break;
546 case Basis631PlusG2dp:
547 gBasis = "N31";
548 extraBasis += " NGAUSS=6 NDFUNC=2 NPFUNC=1 DIFFSP=.TRUE.";
549 break;
550 case Basis6311PlusPlusG2dp:
551 gBasis = "N311";
552 extraBasis += " NGAUSS=6 NDFUNC=2 NPFUNC=1 DIFFSP=.TRUE. DIFFS=.TRUE.";
553 break;
554 case BasisCorePotential:
555 gBasis = "SBK";
556 extraBasis += " NGAUSS=3 NDFUNC=1";
557 extraContrl += " ECP=SBK";
558 break;
559 default:
560 break;
561 }
562 }
563
564 switch (state) {
565 case StateGas:
566 break;
567 case StateWater:
568 pcm = " $PCM SOLVNT=WATER $END\n";
569 break;
570 default:
571 break;
572 }
573
574 switch (multiplicity) {
575 case MultiplicitySinglet:
576 scfTyp = "RHF";
577 mult = "1";
578 break;
579 case MultiplicityDoublet:
580 scfTyp = "ROHF";
581 mult = "2";
582 break;
583 case MultiplicityTriplet:
584 scfTyp = "ROHF";
585 mult = "3";
586 break;
587 default:
588 break;
589 }
590
591 switch (charge) {
592 case ChargeDication:
593 iCharg = "2";
594 break;
595 case ChargeCation:
596 iCharg = "1";
597 break;
598 case ChargeNeutral:
599 iCharg = "0";
600 break;
601 case ChargeAnion:
602 iCharg = "-1";
603 break;
604 case ChargeDianion:
605 iCharg = "-2";
606 break;
607 default:
608 break;
609 }
610
611 // build up the input file:
612 QString file;
613 file += QString("! %1\n").arg(title);
614 file += QString(" $BASIS GBASIS=%1%2 $END\n").arg(gBasis, extraBasis);
615 file += pcm;
616 file += QString(" $CONTRL SCFTYP=%1 RUNTYP=%2 ICHARG=%3 MULT=%4%5 $END\n")
617 .arg(scfTyp, runTyp, iCharg, mult, extraContrl);
618 file += statPt;
619 file += force;
620 file += "\n";
621 file += " $DATA\n";
622 file += "Title\n";
623 file += "C1\n";
624
625 if (m_molecule) {
626 for (size_t i = 0; i < m_molecule->atomCount(); ++i) {
627 Core::Atom atom = m_molecule->atom(i);
628 file += QString("%1 %2 %3 %4 %5\n")
629 .arg(Core::Elements::symbol(atom.atomicNumber()), -3)
630 .arg(static_cast<float>(atom.atomicNumber()), 5, 'f', 1)
631 .arg(atom.position3d().x(), 9, 'f', 5)
632 .arg(atom.position3d().y(), 9, 'f', 5)
633 .arg(atom.position3d().z(), 9, 'f', 5);
634 }
635 }
636
637 file += " $END\n";
638
639 ui.previewText->setText(file);
640 ui.previewText->document()->setModified(false);
641 updateOptionCache();
642 }
643
resetClicked()644 void GamessInputDialog::resetClicked()
645 {
646 setBasicDefaults();
647 updatePreviewText();
648 }
649
defaultsClicked()650 void GamessInputDialog::defaultsClicked()
651 {
652 setBasicDefaults();
653 updatePreviewText();
654 }
655
generateClicked()656 void GamessInputDialog::generateClicked()
657 {
658 QSettings settings;
659 QString fileName =
660 (ui.baseNameEdit->text().isEmpty() ? ui.baseNameEdit->placeholderText()
661 : ui.baseNameEdit->text()) +
662 ".inp";
663 QString targetFile =
664 settings.value("gamessInput/outputDirectory", QDir::homePath()).toString();
665 targetFile =
666 QDir(QFileInfo(targetFile).absoluteDir()).absoluteFilePath(fileName);
667
668 fileName = QFileDialog::getSaveFileName(this, tr("Save GAMESS input file"),
669 targetFile);
670
671 // User cancel:
672 if (fileName.isNull())
673 return;
674
675 settings.setValue("gamessInput/outputDirectory", fileName);
676
677 QFile file(fileName);
678 bool success = false;
679 if (file.open(QFile::WriteOnly | QFile::Text)) {
680 if (file.write(ui.previewText->toPlainText().toLocal8Bit()) > 0) {
681 success = true;
682 }
683 file.close();
684 }
685
686 if (!success) {
687 QMessageBox::critical(this, tr("Output Error"),
688 tr("Failed to write to file %1.").arg(fileName));
689 }
690 }
691
computeClicked()692 void GamessInputDialog::computeClicked()
693 {
694 // Verify that molequeue is running:
695 MoleQueueManager& mqManager = MoleQueueManager::instance();
696 if (!mqManager.connectIfNeeded()) {
697 QMessageBox::information(this, tr("Cannot connect to MoleQueue"),
698 tr("Cannot connect to MoleQueue server. Please "
699 "ensure that it is running and try again."));
700 return;
701 }
702
703 QString description(ui.titleEdit->text());
704 if (description.isEmpty())
705 description = generateJobTitle();
706
707 QString fileNameBase = ui.baseNameEdit->text().isEmpty()
708 ? ui.baseNameEdit->placeholderText()
709 : ui.baseNameEdit->text();
710
711 JobObject job;
712 job.setProgram("GAMESS");
713 job.setDescription(description);
714 job.setInputFile(QString("%1.inp").arg(fileNameBase),
715 ui.previewText->toPlainText());
716
717 MoleQueueDialog::SubmitStatus submitStatus =
718 MoleQueueDialog::submitJob(this, tr("Submit GAMESS Calculation"), job,
719 MoleQueueDialog::WaitForSubmissionResponse |
720 MoleQueueDialog::SelectProgramFromTemplate);
721
722 switch (submitStatus) {
723 default:
724 case MoleQueueDialog::SubmissionSuccessful:
725 case MoleQueueDialog::SubmissionFailed:
726 case MoleQueueDialog::SubmissionAttempted:
727 case MoleQueueDialog::SubmissionAborted:
728 // The dialog handles these cases adequately, we don't need to do
729 // anything.
730 break;
731
732 case MoleQueueDialog::JobFailed:
733 // Inform the user:
734 QMessageBox::information(this, tr("Job Failed"),
735 tr("The job did not complete successfully."),
736 QMessageBox::Ok);
737 break;
738
739 case MoleQueueDialog::JobFinished:
740 // Let the world know that the job is ready to open. job has been
741 // overwritten with the final job details.
742 emit openJobOutput(job);
743 hide();
744 break;
745 }
746 }
747
updateTitlePlaceholder()748 void GamessInputDialog::updateTitlePlaceholder()
749 {
750 ui.titleEdit->setPlaceholderText(generateJobTitle());
751 }
752
753 } // end namespace QtPlugins
754 } // end namespace Avogadro
755