root/src/globalsearch/optbase.cpp @ 06f244abd607a7c31b6ed64e2a2c378638e727d2

Revision 06f244abd607a7c31b6ed64e2a2c378638e727d2, 20.8 KB (checked in by David C. Lonie <loniedavid@…>, 13 months ago)

Added a preoptimization queue for MolecularXtals?.

  • Property mode set to 100644
Line 
1/**********************************************************************
2  OptBase - Base class for global search extensions
3
4  Copyright (C) 2010-2011 by David C. Lonie
5
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation version 2 of the License.
9
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  GNU General Public License for more details.
14 ***********************************************************************/
15
16#include <globalsearch/optbase.h>
17
18#include <globalsearch/bt.h>
19#include <globalsearch/macros.h>
20#include <globalsearch/optimizer.h>
21#include <globalsearch/queuemanager.h>
22#include <globalsearch/queueinterface.h>
23#include <globalsearch/queueinterfaces/local.h>
24#include <globalsearch/queueinterfaces/pbs.h>
25#ifdef ENABLE_SSH
26#include <globalsearch/sshmanager.h>
27#ifdef USE_CLI_SSH
28#include <globalsearch/sshmanager_cli.h>
29#else // USE_CLI_SSH
30#include <globalsearch/sshmanager_libssh.h>
31#endif // USE_CLI_SSH
32#endif // ENABLE_SSH
33#include <globalsearch/structure.h>
34#include <globalsearch/ui/abstractdialog.h>
35
36#include <QtCore/QFile>
37#include <QtCore/QThread>
38
39#include <QtGui/QClipboard>
40#include <QtGui/QMessageBox>
41#include <QtGui/QApplication>
42#include <QtGui/QInputDialog>
43
44using namespace OpenBabel;
45
46namespace GlobalSearch {
47
48  OptBase::OptBase(AbstractDialog *parent) :
49    QObject(parent),
50    m_dialog(parent),
51    m_tracker(new Tracker (this)),
52    m_queueThread(new QThread),
53    m_queue(new QueueManager(m_queueThread, this)),
54    m_queueInterface(0), // This will be set when the GUI is initialized
55    m_optimizer(0),      // This will be set when the GUI is initialized
56#ifdef ENABLE_SSH
57    m_ssh(NULL),
58#endif // ENABLE_SSH
59    m_idString("Generic"),
60    sOBMutex(new QMutex),
61    stateFileMutex(new QMutex),
62    backTraceMutex(new QMutex),
63    usePreopt(false),
64    savePending(false),
65    readOnly(false),
66    testingMode(false),
67    test_nRunsStart(1),
68    test_nRunsEnd(100),
69    test_nStructs(600),
70    cutoff(-1),
71    m_schemaVersion(1)
72  {
73    // Connections
74    connect(this, SIGNAL(sessionStarted()),
75            m_queueThread, SLOT(start()),
76            Qt::DirectConnection);
77    connect(this, SIGNAL(startingSession()),
78            m_queueThread, SLOT(start()),
79            Qt::DirectConnection);
80    connect(this, SIGNAL(startingSession()),
81            this, SLOT(setIsStartingTrue()),
82            Qt::DirectConnection);
83    connect(this, SIGNAL(sessionStarted()),
84            this, SLOT(setIsStartingFalse()),
85            Qt::DirectConnection);
86    connect(this, SIGNAL(readOnlySessionStarted()),
87            this, SLOT(setIsStartingFalse()),
88            Qt::DirectConnection);
89    connect(this, SIGNAL(needBoolean(const QString&, bool*)),
90            this, SLOT(promptForBoolean(const QString&, bool*)),
91            Qt::BlockingQueuedConnection); // Wait until slot returns
92    connect(this, SIGNAL(needPassword(const QString&, QString*, bool*)),
93            this, SLOT(promptForPassword(const QString&, QString*, bool*)),
94            Qt::BlockingQueuedConnection); // Wait until slot returns
95    connect(this, SIGNAL(sig_setClipboard(const QString&)),
96            this, SLOT(setClipboard_(const QString&)),
97            Qt::QueuedConnection);
98
99    INIT_RANDOM_GENERATOR();
100  }
101
102  OptBase::~OptBase()
103  {
104    delete m_queue;
105    m_queue = 0;
106
107    if (m_queueThread && m_queueThread->isRunning()) {
108      m_queueThread->wait();
109    }
110    delete m_queueThread;
111    m_queueThread = 0;
112
113    delete m_optimizer;
114    m_optimizer = 0;
115
116    delete m_queueInterface;
117    m_queueInterface = 0;
118
119    delete m_tracker;
120    m_tracker = 0;
121  }
122
123  void OptBase::reset() {
124    m_tracker->lockForWrite();
125    m_tracker->deleteAllStructures();
126    m_tracker->reset();
127    m_tracker->unlock();
128    m_queue->reset();
129  }
130
131#ifdef ENABLE_SSH
132  bool OptBase::createSSHConnections()
133  {
134#ifdef USE_CLI_SSH
135    return this->createSSHConnections_cli();
136#else // USE_CLI_SSH
137    return this->createSSHConnections_libssh();
138#endif // USE_CLI_SSH
139  }
140#endif // ENABLE_SSH
141
142  void OptBase::printBackTrace() {
143    backTraceMutex->lock();
144    QStringList l = getBackTrace();
145    backTraceMutex->unlock();
146    for (int i = 0; i < l.size();i++)
147      qDebug() << l.at(i);
148  }
149
150  QList<double> OptBase::getProbabilityList(const QList<Structure*> &structures) {
151    // IMPORTANT: structures must contain one more structure than
152    // needed -- the last structure in the list will be removed from
153    // the probability list!
154    if (structures.size() <= 2) {
155      return QList<double>();
156    }
157
158    QList<double> probs;
159    Structure *s=0, *first=0, *last=0;
160    first = structures.first();
161    last = structures.last();
162    first->lock()->lockForRead();
163    last->lock()->lockForRead();
164    double lowest = first->getEnthalpy();
165    double highest = last->getEnthalpy();;
166    double spread = highest - lowest;
167    last->lock()->unlock();
168    first->lock()->unlock();
169    // If all structures are at the same enthalpy, lets save some time...
170    if (spread <= 1e-5) {
171      double dprob = 1.0/static_cast<double>(structures.size()-1);
172      double prob = 0;
173      for (int i = 0; i < structures.size()-1; i++) {
174        probs.append(prob);
175        prob += dprob;
176      }
177      return probs;
178    }
179    // Generate a list of floats from 0->1 proportional to the enthalpies;
180    // E.g. if enthalpies are:
181    // -5   -2   -1   3   5
182    // We'll have:
183    // 0   0.3  0.4  0.8  1
184    for (int i = 0; i < structures.size(); i++) {
185      s = structures.at(i);
186      s->lock()->lockForRead();
187      probs.append( ( s->getEnthalpy() - lowest ) / spread);
188      s->lock()->unlock();
189    }
190    // Subtract each value from one, and find the sum of the resulting list
191    // We'll end up with:
192    // 1  0.7  0.6  0.2  0   --   sum = 2.5
193    double sum = 0;
194    for (int i = 0; i < probs.size(); i++){
195      probs[i] = 1.0 - probs.at(i);
196      sum += probs.at(i);
197    }
198    // Normalize with the sum so that the list adds to 1
199    // 0.4  0.28  0.24  0.08  0
200    for (int i = 0; i < probs.size(); i++){
201      probs[i] /= sum;
202    }
203    // Then replace each entry with a cumulative total:
204    // 0.4 0.68 0.92 1 1
205    sum = 0;
206    for (int i = 0; i < probs.size(); i++){
207      sum += probs.at(i);
208      probs[i] = sum;
209    }
210    // Pop off the last entry (remember the n_popSize + 1 earlier?)
211    // 0.4 0.68 0.92 1
212    probs.removeLast();
213    // And we have a enthalpy weighted probability list! To use:
214    //
215    //   double r = rand.NextFloat();
216    //   uint ind;
217    //   for (ind = 0; ind < probs.size(); ind++)
218    //     if (r < probs.at(ind)) break;
219    //
220    // ind will hold the chosen index.
221    return probs;
222  }
223
224  bool OptBase::save(const QString &stateFilename, bool notify)
225  {
226    if (isStarting ||
227        readOnly) {
228      savePending = false;
229      return false;
230    }
231    QReadLocker trackerLocker (m_tracker->rwLock());
232    QMutexLocker locker (stateFileMutex);
233    QString filename;
234    if (stateFilename.isEmpty()) {
235      filename = filePath + "/" + m_idString.toLower() + ".state";
236    }
237    else {
238      filename = stateFilename;
239    }
240    QString oldfilename = filename + ".old";
241
242    if (notify) {
243      if (!m_dialog->startProgressUpdate(tr("Saving: Writing %1...")
244                                         .arg(filename),
245                                         0, 0)) {
246        // The progress bar is already in use -- disable notifications
247        notify = false;
248      }
249    }
250
251    // Copy .state -> .state.old
252    if (QFile::exists(filename) ) {
253      if (QFile::exists(oldfilename)) {
254        QFile::remove(oldfilename);
255      }
256      QFile::copy(filename, oldfilename);
257    }
258
259    SETTINGS(filename);
260    const int VERSION = m_schemaVersion;
261    settings->beginGroup(m_idString.toLower());
262    settings->setValue("version",          VERSION);
263    settings->setValue("saveSuccessful", false);
264    settings->endGroup();
265
266    // Write/update .state
267    m_dialog->writeSettings(filename);
268
269    // Loop over structures and save them
270    QList<Structure*> *structures = m_tracker->list();
271
272    QString structureStateFileName;
273
274    Structure* structure;
275    for (int i = 0; i < structures->size(); i++) {
276      structure = structures->at(i);
277      structure->lock()->lockForRead();
278      // Set index here -- this is the only time these are written, so
279      // this is "ok" under a read lock because of the savePending logic
280      structure->setIndex(i);
281      structureStateFileName = structure->fileName() + "/structure.state";
282      if (notify) {
283        m_dialog->updateProgressLabel(tr("Saving: Writing %1...")
284                                      .arg(structureStateFileName));
285      }
286      structure->writeSettings(structureStateFileName);
287      structure->lock()->unlock();
288    }
289
290    /////////////////////////
291    // Print results files //
292    /////////////////////////
293
294    QFile file (filePath + "/results.txt");
295    QFile oldfile (filePath + "/results_old.txt");
296    if (notify) {
297      m_dialog->updateProgressLabel(tr("Saving: Writing %1...")
298                                    .arg(file.fileName()));
299    }
300    if (oldfile.open(QIODevice::ReadOnly))
301      oldfile.remove();
302    if (file.open(QIODevice::ReadOnly))
303      file.copy(oldfile.fileName());
304    file.close();
305    if (!file.open(QIODevice::WriteOnly)) {
306      error("OptBase::save(): Error opening file "+file.fileName()+" for writing...");
307      savePending = false;
308      return false;
309    }
310    QTextStream out (&file);
311
312    QList<Structure*> sortedStructures;
313
314    for (int i = 0; i < structures->size(); i++)
315      sortedStructures.append(structures->at(i));
316    if (sortedStructures.size() != 0) {
317      Structure::sortAndRankByEnthalpy(&sortedStructures);
318      out << sortedStructures.first()->getResultsHeader() << endl;
319    }
320
321    for (int i = 0; i < sortedStructures.size(); i++) {
322      structure = sortedStructures.at(i);
323      if (!structure) continue; // In case there was a problem copying.
324      structure->lock()->lockForRead();
325      out << structure->getResultsEntry() << endl;
326      structure->lock()->unlock();
327      if (notify) {
328        m_dialog->stopProgressUpdate();
329      }
330    }
331
332    // Mark operation successful
333    settings->setValue(m_idString.toLower() + "/saveSuccessful", true);
334    DESTROY_SETTINGS(filename);
335
336    savePending = false;
337    return true;
338  }
339
340  QString OptBase::interpretTemplate(const QString & str, Structure* structure)
341  {
342    QStringList list = str.split("%");
343    QString line;
344    QString origLine;
345    for (int line_ind = 0; line_ind < list.size(); line_ind++) {
346      origLine = line = list.at(line_ind);
347      interpretKeyword_base(line, structure);
348      // Add other interpret keyword sections here if needed when subclassing
349      if (line != origLine) { // Line was a keyword
350        list.replace(line_ind, line);
351      }
352    }
353    // Rejoin string
354    QString ret = list.join("");
355    ret += "\n";
356    return ret;
357  }
358
359  void OptBase::interpretKeyword_base(QString &line, Structure* structure)
360  {
361    QString rep = "";
362    // User data
363    if (line == "user1")                rep += optimizer()->getUser1();
364    else if (line == "user2")           rep += optimizer()->getUser2();
365    else if (line == "user3")           rep += optimizer()->getUser3();
366    else if (line == "user4")           rep += optimizer()->getUser4();
367    else if (line == "description")     rep += description;
368    else if (line == "percent")         rep += "%";
369
370    // Structure specific data
371    if (line == "coords") {
372      QList<Avogadro::Atom*> atoms = structure->atoms();
373      QList<Avogadro::Atom*>::const_iterator it;
374      int optIndex = -1;
375      QHash<int, int> *lut = structure->getOptimizerLookupTable();
376      lut->clear();
377      const Eigen::Vector3d *vec;
378      for (it  = atoms.begin();
379           it != atoms.end();
380           it++) {
381        rep += QString(OpenBabel::etab.GetSymbol((*it)->atomicNumber()))+ " ";
382        vec = (*it)->pos();
383        rep += QString::number(vec->x()) + " ";
384        rep += QString::number(vec->y()) + " ";
385        rep += QString::number(vec->z()) + "\n";
386        lut->insert(++optIndex, (*it)->index());
387      }
388    }
389    else if (line == "coordsInternalFlags") {
390      QList<Avogadro::Atom*> atoms = structure->atoms();
391      QList<Avogadro::Atom*>::const_iterator it;
392      const Eigen::Vector3d *vec;
393      int optIndex = -1;
394      QHash<int, int> *lut = structure->getOptimizerLookupTable();
395      lut->clear();
396      for (it  = atoms.begin();
397           it != atoms.end();
398           it++) {
399        rep += QString(OpenBabel::etab.GetSymbol((*it)->atomicNumber()))+ " ";
400        vec = (*it)->pos();
401        rep += QString::number(vec->x()) + " 1 ";
402        rep += QString::number(vec->y()) + " 1 ";
403        rep += QString::number(vec->z()) + " 1\n";
404        lut->insert(++optIndex, (*it)->index());
405      }
406    }
407    else if (line == "coordsSuffixFlags") {
408      QList<Avogadro::Atom*> atoms = structure->atoms();
409      QList<Avogadro::Atom*>::const_iterator it;
410      const Eigen::Vector3d *vec;
411      int optIndex = -1;
412      QHash<int, int> *lut = structure->getOptimizerLookupTable();
413      lut->clear();
414      for (it  = atoms.begin();
415           it != atoms.end();
416           it++) {
417        rep += QString(OpenBabel::etab.GetSymbol((*it)->atomicNumber()))+ " ";
418        vec = (*it)->pos();
419        rep += QString::number(vec->x()) + " ";
420        rep += QString::number(vec->y()) + " ";
421        rep += QString::number(vec->z()) + " 1 1 1\n";
422        lut->insert(++optIndex, (*it)->index());
423      }
424    }
425    else if (line == "coordsId") {
426      QList<Avogadro::Atom*> atoms = structure->atoms();
427      QList<Avogadro::Atom*>::const_iterator it;
428      const Eigen::Vector3d *vec;
429      int optIndex = -1;
430      QHash<int, int> *lut = structure->getOptimizerLookupTable();
431      lut->clear();
432      for (it  = atoms.begin();
433           it != atoms.end();
434           it++) {
435        rep += QString(OpenBabel::etab.GetSymbol((*it)->atomicNumber()))+ " ";
436        rep += QString::number((*it)->atomicNumber()) + " ";
437        vec = (*it)->pos();
438        rep += QString::number(vec->x()) + " ";
439        rep += QString::number(vec->y()) + " ";
440        rep += QString::number(vec->z()) + "\n";
441        lut->insert(++optIndex, (*it)->index());
442      }
443    }
444    else if (line == "numAtoms")        rep += QString::number(structure->numAtoms());
445    else if (line == "numSpecies")      rep += QString::number(structure->getSymbols().size());
446    else if (line == "filename")        rep += structure->fileName();
447    else if (line == "rempath")         rep += structure->getRempath();
448    else if (line == "gen")             rep += QString::number(structure->getGeneration());
449    else if (line == "id")              rep += QString::number(structure->getIDNumber());
450    else if (line == "incar")           rep += QString::number(structure->getCurrentOptStep());
451    else if (line == "optStep")         rep += QString::number(structure->getCurrentOptStep());
452
453    if (!rep.isEmpty()) {
454      // Remove any trailing newlines
455      rep = rep.replace(QRegExp("\n$"), "");
456      line = rep;
457    }
458  }
459
460  QString OptBase::getTemplateKeywordHelp_base()
461  {
462    QString str;
463    QTextStream out (&str);
464    out
465      << "The following keywords should be used instead of the indicated variable data:\n"
466      << "\n"
467      << "Misc:\n"
468      << "%percent% -- Literal percent sign (needed for CASTEP!)\n"
469      << "\n"
470      << "User data:\n"
471      << "%userX% -- User specified value, where X = 1, 2, 3, or 4\n"
472      << "%description% -- Optimization description\n"
473      << "\n"
474      << "Atomic coordinate formats for isolated structures:\n"
475      << "%coords% -- cartesian coordinates\n\t[symbol] [x] [y] [z]\n"
476      << "%coordsInternalFlags% -- cartesian coordinates; flag after each coordinate\n\t[symbol] [x] 1 [y] 1 [z] 1\n"
477      << "%coordsSuffixFlags% -- cartesian coordinates; flags after all coordinates\n\t[symbol] [x] [y] [z] 1 1 1\n"
478      << "%coordsId% -- cartesian coordinates with atomic number\n\t[symbol] [atomic number] [x] [y] [z]\n"
479      << "\n"
480      << "Generic structure data:\n"
481      << "%numAtoms% -- Number of atoms in unit cell\n"
482      << "%numSpecies% -- Number of unique atomic species in unit cell\n"
483      << "%filename% -- local output filename\n"
484      << "%rempath% -- path to structure's remote directory\n"
485      << "%gen% -- structure generation number (if relevant)\n"
486      << "%id% -- structure id number\n"
487      << "%optStep% -- current optimization step\n"
488      ;
489    return str;
490  }
491
492  void OptBase::setOptimizer(Optimizer *o)
493  {
494    m_optimizer = o;
495    emit optimizerChanged(o);
496  }
497
498  void OptBase::setQueueInterface(QueueInterface *q)
499  {
500    m_queueInterface = q;
501    emit queueInterfaceChanged(q);
502  }
503
504  void OptBase::promptForPassword(const QString &message,
505                                  QString *newPassword,
506                                  bool *ok)
507  {
508    (*newPassword) = QInputDialog::getText(dialog(), "Need password:", message,
509                                           QLineEdit::Password, QString(), ok);
510  };
511
512  void OptBase::promptForBoolean(const QString &message,
513                                 bool *ok)
514  {
515    if (QMessageBox::question(dialog(), m_idString, message,
516                              QMessageBox::Yes | QMessageBox::No)
517        == QMessageBox::Yes) {
518      *ok = true;
519    } else {
520      *ok = false;
521    }
522  }
523
524  void OptBase::setClipboard(const QString &text) const
525  {
526    emit sig_setClipboard(text);
527  }
528
529  // No need to document this
530  /// @cond
531  void OptBase::setClipboard_(const QString &text) const
532  {
533    // Set to system clipboard
534    QApplication::clipboard()->setText(text, QClipboard::Clipboard);
535    // For middle-click on X11
536    if (QApplication::clipboard()->supportsSelection()) {
537      QApplication::clipboard()->setText(text, QClipboard::Selection);
538    }
539  }
540  /// @endcond
541
542#ifdef ENABLE_SSH
543#ifndef USE_CLI_SSH
544
545  bool OptBase::createSSHConnections_libssh()
546  {
547    delete m_ssh;
548    SSHManagerLibSSH *libsshManager = new SSHManagerLibSSH(5, this);
549    m_ssh = libsshManager;
550    QString pw = "";
551    for (;;) {
552      try {
553        libsshManager->makeConnections(host, username, pw, port);
554      }
555      catch (SSHConnection::SSHConnectionException e) {
556        QString err;
557        switch (e) {
558        case SSHConnection::SSH_CONNECTION_ERROR:
559        case SSHConnection::SSH_UNKNOWN_ERROR:
560        default:
561          err = "There was a problem connection to the ssh server at "
562              + username + "@" + host + ":" + QString::number(port) + ". "
563              "Please check that all provided information is correct, and "
564              "attempt to log in outside of Avogadro before trying again.";
565          error(err);
566          return false;
567        case SSHConnection::SSH_UNKNOWN_HOST_ERROR: {
568          // The host is not known, or has changed its key.
569          // Ask user if this is ok.
570          err = "The host "
571            + host + ":" + QString::number(port)
572            + " either has an unknown key, or has changed it's key:\n"
573            + libsshManager->getServerKeyHash() + "\n"
574            + "Would you like to trust the specified host?";
575          bool ok;
576          // This is a BlockingQueuedConnection, which blocks until
577          // the slot returns.
578          emit needBoolean(err, &ok);
579          if (!ok) { // user cancels
580            return false;
581          }
582          libsshManager->validateServerKey();
583          continue;
584        } // end case
585        case SSHConnection::SSH_BAD_PASSWORD_ERROR: {
586          // Chances are that the pubkey auth was attempted but failed,
587          // so just prompt user for password.
588          err = "Please enter a password for "
589            + username + "@" + host + ":" + QString::number(port)
590            + ":";
591          bool ok;
592          QString newPassword;
593          // This is a BlockingQueuedConnection, which blocks until
594          // the slot returns.
595          emit needPassword(err, &newPassword, &ok);
596          if (!ok) { // user cancels
597            return false;
598          }
599          pw = newPassword;
600          continue;
601        } // end case
602        } // end switch
603      } // end catch
604      break;
605    } // end forever
606    return true;
607  }
608
609#else // not USE_CLI_SSH
610
611  bool OptBase::createSSHConnections_cli()
612  {
613    // Since we rely on public key auth here, it's much easier to set up:
614    SSHManagerCLI *cliSSHManager = new SSHManagerCLI(5, this);
615    cliSSHManager->makeConnections(host, username, "", port);
616    m_ssh = cliSSHManager;
617    return true;
618  }
619
620#endif // not USE_CLI_SSH
621#endif // ENABLE_SSH
622
623  void OptBase::warning(const QString & s) {
624    qWarning() << "Warning: " << s;
625    emit warningStatement(s);
626  }
627
628  void OptBase::debug(const QString & s) {
629    qDebug() << "Debug: " << s;
630    emit debugStatement(s);
631  }
632
633  void OptBase::error(const QString & s) {
634    qWarning() << "Error: " << s;
635    emit errorStatement(s);
636  }
637
638} // end namespace GlobalSearch
639
Note: See TracBrowser for help on using the browser.