root/src/xtalopt/structures/xtal.cpp @ 06f244abd607a7c31b6ed64e2a2c378638e727d2

Revision 06f244abd607a7c31b6ed64e2a2c378638e727d2, 55.6 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  Xtal - Wrapper for Structure to ease work with crystals.
3
4  Copyright (C) 2009-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 <xtalopt/structures/xtal.h>
17
18#include <xtalopt/xtalopt.h>
19
20#include <avogadro/primitive.h>
21#include <avogadro/molecule.h>
22#include <avogadro/atom.h>
23
24#include <globalsearch/macros.h>
25#include <globalsearch/obeigenconv.h>
26#include <globalsearch/stablecomparison.h>
27
28#include <openbabel/generic.h>
29#include <openbabel/forcefield.h>
30
31extern "C" {
32#include <spglib/spglib.h>
33}
34
35#include <xtalcomp/xtalcomp.h>
36
37#include <Eigen/LU>
38
39#include <QtAlgorithms>
40
41#include <QtCore/QFile>
42#include <QtCore/QDebug>
43#include <QtCore/QRegExp>
44#include <QtCore/QStringList>
45
46#define DEBUG_MATRIX(m) printf("| %9.5f %9.5f %9.5f |\n"        \
47                               "| %9.5f %9.5f %9.5f |\n"        \
48                               "| %9.5f %9.5f %9.5f |\n",       \
49                               (m)(0,0), (m)(0,1), (m)(0,2),    \
50                               (m)(1,0), (m)(1,1), (m)(1,2),    \
51                               (m)(2,0), (m)(2,1), (m)(2,2))
52
53using namespace std;
54using namespace OpenBabel;
55using namespace Avogadro;
56using namespace Eigen;
57
58namespace XtalOpt {
59
60  Xtal::Xtal(QObject *parent) :
61    Structure(parent)
62  {
63    ctor(parent);
64  }
65
66  Xtal::Xtal(double A, double B, double C,
67             double Alpha, double Beta, double Gamma,
68             QObject *parent) :
69  Structure(parent)
70  {
71    ctor(parent);
72    setCellInfo(A, B, C, Alpha, Beta, Gamma);
73  }
74
75  // Actual constructor:
76  void Xtal::ctor(QObject *parent)
77  {
78    if (!m_mixMatrices.size()) {
79      generateValidCOBs();
80    }
81    m_spgNumber = 231;
82    this->setOBUnitCell(new OpenBabel::OBUnitCell);
83    // Openbabel seems to be fond of making unfounded assumptions that
84    // break things. This fixes one of them.
85    this->cell()->SetSpaceGroup(0);
86  }
87
88  Xtal::~Xtal() {
89  }
90
91  void Xtal::setVolume(double  Volume) {
92
93    // Get scaling factor
94    double factor = pow(Volume/cell()->GetCellVolume(), 1.0/3.0); // Cube root
95
96    // Store position of atoms in fractional units
97    QList<Atom*> atomList       = atoms();
98    QList<Vector3d*> fracCoordsList;
99    for (int i = 0; i < atomList.size(); i++)
100      fracCoordsList.append(cartToFrac(atomList.at(i)->pos()));
101
102    // Scale cell
103    setCellInfo(factor * cell()->GetA(),
104                factor * cell()->GetB(),
105                factor * cell()->GetC(),
106                cell()->GetAlpha(),
107                cell()->GetBeta(),
108                cell()->GetGamma());
109
110    // Recalculate coordinates:
111    for (int i = 0; i < atomList.size(); i++)
112      atomList.at(i)->setPos(fracToCart(*(fracCoordsList.at(i))));
113
114    // Free memory
115    qDeleteAll(fracCoordsList);
116  }
117
118  void Xtal::rescaleCell(double a, double b, double c,
119                         double alpha, double beta, double gamma)
120  {
121    if (!a && !b && !c && !alpha && !beta && !gamma) {
122      return;
123    }
124
125    this->rotateCellAndCoordsToStandardOrientation();
126
127    // Store position of atoms in fractional units
128    QList<Atom*> atomList       = atoms();
129    QList<Vector3d> fracCoordsList;
130    for (int i = 0; i < atomList.size(); i++)
131      fracCoordsList.append(cartToFrac(*atomList.at(i)->pos()));
132
133    double nA = getA();
134    double nB = getB();
135    double nC = getC();
136    double nAlpha = getAlpha();
137    double nBeta = getBeta();
138    double nGamma = getGamma();
139
140    // Set angles and reset volume
141    if (alpha || beta || gamma) {
142      double volume = getVolume();
143      if (alpha) nAlpha = alpha;
144      if (beta) nBeta = beta;
145      if (gamma) nGamma = gamma;
146      setCellInfo(nA,
147                  nB,
148                  nC,
149                  nAlpha,
150                  nBeta,
151                  nGamma);
152      setVolume(volume);
153    }
154
155    if (a || b || c) {
156      // Initialize variables
157      double scale_primary, scale_secondary, scale_tertiary;
158
159      // Set lengths while preserving volume
160      // Case one length is static
161      if (a && !b && !c) {        // A
162        scale_primary = a / nA;
163        scale_secondary = pow(scale_primary, 0.5);
164        nA = a;
165        nB *= scale_secondary;
166        nC *= scale_secondary;
167      }
168      else if (!a && b && !c) {   // B
169        scale_primary = b / nB;
170        scale_secondary = pow(scale_primary, 0.5);
171        nB = b;
172        nA *= scale_secondary;
173        nC *= scale_secondary;
174      }
175      else if (!a && !b && c) {   // C
176        scale_primary = c / nC;
177        scale_secondary = pow(scale_primary, 0.5);
178        nC = c;
179        nA *= scale_secondary;
180        nB *= scale_secondary;
181      }
182      // Case two lengths are static
183      else if (a && b && !c) {    // AB
184        scale_primary   = a / nA;
185        scale_secondary = b / nB;
186        scale_tertiary  = scale_primary * scale_secondary;
187        nA = a;
188        nB = b;
189        nC *= scale_tertiary;
190      }
191      else if (a && !b && c) {    // AC
192        scale_primary   = a / nA;
193        scale_secondary = c / nC;
194        scale_tertiary  = scale_primary * scale_secondary;
195        nA = a;
196        nC = c;
197        nB *= scale_tertiary;
198      }
199      else if (!a && b && c) {    // BC
200        scale_primary   = c / nC;
201        scale_secondary = b / nB;
202        scale_tertiary  = scale_primary * scale_secondary;
203        nC = c;
204        nB = b;
205        nA *= scale_tertiary;
206      }
207      // Case three lengths are static
208      else if (a && b && c) {     // ABC
209        nA = a;
210        nB = b;
211        nC = c;
212      }
213
214      // Update unit cell
215      setCellInfo(nA,
216                  nB,
217                  nC,
218                  nAlpha,
219                  nBeta,
220                  nGamma);
221    }
222
223    // Recalculate coordinates:
224    for (int i = 0; i < atomList.size(); i++)
225      atomList.at(i)->setPos(fracToCart(fracCoordsList.at(i)));
226  }
227
228  bool Xtal::niggliReduce(const unsigned int iterations)
229  {
230    // Cache volume for later sanity checks
231    const double origVolume = cell()->GetCellVolume();
232
233    // Grab lattice vectors
234    const std::vector<OpenBabel::vector3> obvecs = cell()->GetCellVectors();
235    const Eigen::Vector3d v1 (OB2Eigen(obvecs[0]));
236    const Eigen::Vector3d v2 (OB2Eigen(obvecs[1]));
237    const Eigen::Vector3d v3 (OB2Eigen(obvecs[2]));
238
239    // Compute characteristic (step 0)
240    double A    = v1.squaredNorm();
241    double B    = v2.squaredNorm();
242    double C    = v3.squaredNorm();
243    double xi   = 2*v2.dot(v3);
244    double eta  = 2*v1.dot(v3);
245    double zeta = 2*v1.dot(v2);
246
247    // Return value
248    bool ret = false;
249
250    // comparison tolerance
251    double tol = STABLE_COMP_TOL * pow(this->getVolume(), 1.0/3.0);
252
253    // Initialize change of basis matrices:
254    //
255    // Although the reduction algorithm produces quantities directly
256    // relatible to a,b,c,alpha,beta,gamma, we will calculate a change
257    // of basis matrix to use instead, and discard A, B, C, xi, eta,
258    // zeta. By multiplying the change of basis matrix against the
259    // current cell matrix, we avoid the problem of handling the
260    // orientation matrix already present in the cell. The inverse of
261    // this matrix can also be used later to convert the atomic
262    // positions.
263    // tmpMat is used to build other matrices
264    Eigen::Matrix3d tmpMat;
265
266    // Cache static matrices:
267
268    // Swap x,y (Used in Step 1). Negatives ensure proper sign of final
269    // determinant.
270    tmpMat << 0,-1,0, -1,0,0, 0,0,-1;
271    const Eigen::Matrix3d C1(tmpMat);
272    // Swap y,z (Used in Step 2). Negatives ensure proper sign of final
273    // determinant
274    tmpMat << -1,0,0, 0,0,-1, 0,-1,0;
275    const Eigen::Matrix3d C2(tmpMat);
276    // For step 8:
277    tmpMat << 1,0,1, 0,1,1, 0,0,1;
278    const Eigen::Matrix3d C8(tmpMat);
279
280    // initial change of basis matrix
281    tmpMat << 1,0,0, 0,1,0, 0,0,1;
282    Eigen::Matrix3d cob(tmpMat);
283
284    // Enable debugging output here:
285//#define NIGGLI_DEBUG(step) qDebug() << iter << step << A << B << C << xi << eta << zeta << cob.determinant(); \
286//std::cout << cob << std::endl;
287#define NIGGLI_DEBUG(step)
288
289    unsigned int iter;
290    for (iter = 0; iter < iterations; ++iter) {
291      Q_ASSERT(fabs(cob.determinant() - 1.0) < 1e-5);
292      // Step 1:
293      if (
294          StableComp::gt(A, B, tol)
295          || (
296              StableComp::eq(A, B, tol)
297              &&
298              StableComp::gt(fabs(xi), fabs(eta), tol)
299              )
300          ) {
301        cob *= C1;
302        qSwap(A, B);
303        qSwap(xi, eta);
304        NIGGLI_DEBUG(1);
305        ++iter;
306      }
307
308      // Step 2:
309      if (
310          StableComp::gt(B, C, tol)
311          || (
312              StableComp::eq(B, C, tol)
313              &&
314              StableComp::gt(fabs(eta), fabs(zeta), tol)
315              )
316          ) {
317        cob *= C2;
318        qSwap(B, C);
319        qSwap(eta, zeta);
320        NIGGLI_DEBUG(2);
321        continue;
322      }
323
324      // Step 3:
325      // Use exact comparisons in steps 3 and 4.
326      if (xi*eta*zeta > 0) {
327        // Update change of basis matrix:
328        tmpMat <<
329           StableComp::sign(xi),0,0,
330           0,StableComp::sign(eta),0,
331           0,0,StableComp::sign(zeta);
332        cob *= tmpMat;
333
334        // Update characteristic
335        xi   = fabs(xi);
336        eta  = fabs(eta);
337        zeta = fabs(zeta);
338        NIGGLI_DEBUG(3);
339        ++iter;
340      }
341
342      // Step 4:
343      // Use exact comparisons for steps 3 and 4
344      else { // either step 3 or 4 should run
345        // Update change of basis matrix:
346        double *p = NULL;
347        double i = 1;
348        double j = 1;
349        double k = 1;
350        if (xi > 0) {
351          i = -1;
352        }
353        else if (!(xi < 0)) {
354          p = &i;
355        }
356        if (eta > 0) {
357          j = -1;
358        }
359        else if (!(eta < 0)) {
360          p = &j;
361        }
362        if (zeta > 0) {
363          k = -1;
364        }
365        else if (!(zeta < 0)) {
366          p = &k;
367        }
368        if (i*j*k < 0) {
369          if (!p) {
370            std::cerr << "XtalComp warning: one of the input structures "
371                         "contains a lattice that is confusing the Niggli "
372                         "reduction algorithm. Try making a small perturbation "
373                         "(approx. 2 orders of magnitude smaller than the "
374                         "tolerance) to the input lattices and try again. The "
375                         "results of this comparison should not be relied upon."
376                         "\n";
377            return false;
378          }
379          *p = -1;
380        }
381        tmpMat <<i,0,0, 0,j,0, 0,0,k;
382        cob *= tmpMat;
383
384        // Update characteristic
385        xi   = -fabs(xi);
386        eta  = -fabs(eta);
387        zeta = -fabs(zeta);
388        NIGGLI_DEBUG(4);
389        ++iter;
390      }
391
392      // Step 5:
393      if (StableComp::gt(fabs(xi), B, tol)
394          || (StableComp::eq(xi, B, tol)
395              && StableComp::lt(2*eta, zeta, tol)
396              )
397          || (StableComp::eq(xi, -B, tol)
398              && StableComp::lt(zeta, 0, tol)
399              )
400          ) {
401        double signXi = StableComp::sign(xi);
402        // Update change of basis matrix:
403        tmpMat << 1,0,0, 0,1,-signXi, 0,0,1;
404        cob *= tmpMat;
405
406        // Update characteristic
407        C    = B + C - xi*signXi;
408        eta  = eta - zeta*signXi;
409        xi   = xi -   2*B*signXi;
410        NIGGLI_DEBUG(5);
411        continue;
412      }
413
414      // Step 6:
415      if (StableComp::gt(fabs(eta), A, tol)
416          || (StableComp::eq(eta, A, tol)
417              && StableComp::lt(2*xi, zeta, tol)
418              )
419          || (StableComp::eq(eta, -A, tol)
420              && StableComp::lt(zeta, 0, tol)
421              )
422          ) {
423        double signEta = StableComp::sign(eta);
424        // Update change of basis matrix:
425        tmpMat << 1,0,-signEta, 0,1,0, 0,0,1;
426        cob *= tmpMat;
427
428        // Update characteristic
429        C    = A + C - eta*signEta;
430        xi   = xi - zeta*signEta;
431        eta  = eta - 2*A*signEta;
432        NIGGLI_DEBUG(6);
433        continue;
434      }
435
436      // Step 7:
437      if (StableComp::gt(fabs(zeta), A, tol)
438          || (StableComp::eq(zeta, A, tol)
439              && StableComp::lt(2*xi, eta, tol)
440              )
441          || (StableComp::eq(zeta, -A, tol)
442              && StableComp::lt(eta, 0, tol)
443              )
444          ) {
445        double signZeta = StableComp::sign(zeta);
446        // Update change of basis matrix:
447        tmpMat << 1,-signZeta,0, 0,1,0, 0,0,1;
448        cob *= tmpMat;
449
450        // Update characteristic
451        B    = A + B - zeta*signZeta;
452        xi   = xi - eta*signZeta;
453        zeta = zeta - 2*A*signZeta;
454        NIGGLI_DEBUG(7);
455        continue;
456      }
457
458      // Step 8:
459      double sumAllButC = A + B + xi + eta + zeta;
460      if (StableComp::lt(sumAllButC, 0, tol)
461          || (StableComp::eq(sumAllButC, 0, tol)
462              && StableComp::gt(2*(A+eta)+zeta, 0, tol)
463              )
464          ) {
465        // Update change of basis matrix:
466        cob *= C8;
467
468        // Update characteristic
469        C    = sumAllButC + C;
470        xi = 2*B + xi + zeta;
471        eta  = 2*A + eta + zeta;
472        NIGGLI_DEBUG(8);
473        continue;
474      }
475
476      // Done!
477      NIGGLI_DEBUG(999);
478      ret = true;
479      break;
480    }
481
482    // No change, already reduced. Just return.
483    if (iter == 0) {
484      return true;
485    }
486
487    // iterations exceeded
488    if (!ret) {
489      return false;
490    }
491
492    Q_ASSERT_X(cob.determinant() == 1, Q_FUNC_INFO,
493               "Determinant of change of basis matrix must be 1.");
494
495    // Update cell
496    setCellInfo( Eigen2OB(cob).transpose() * cell()->GetCellMatrix());
497
498    // Check that volume has not changed
499    Q_ASSERT_X(StableComp::eq(origVolume, cell()->GetCellVolume(), tol),
500               Q_FUNC_INFO, "Cell volume changed during Niggli reduction.");
501
502    // Rotate and wrap
503    rotateCellAndCoordsToStandardOrientation();
504    wrapAtomsToCell();
505    return true;
506  }
507
508  bool Xtal::isNiggliReduced() const
509  {
510    // cache params
511    double a     = getA();
512    double b     = getB();
513    double c     = getC();
514    double alpha = getAlpha();
515    double beta  = getBeta();
516    double gamma = getGamma();
517
518    return Xtal::isNiggliReduced(a, b, c, alpha, beta, gamma);
519  }
520
521  bool Xtal::isNiggliReduced(const double a, const double b, const double c,
522                             const double alpha, const double beta, const double gamma)
523  {
524    // Calculate characteristic
525    double A    = a*a;
526    double B    = b*b;
527    double C    = c*c;
528    double xi   = 2*b*c*cos(alpha * DEG_TO_RAD);
529    double eta  = 2*a*c*cos(beta * DEG_TO_RAD);
530    double zeta = 2*a*b*cos(gamma * DEG_TO_RAD);
531
532    // comparison tolerance
533    double tol = STABLE_COMP_TOL * ( (a + b + c) * (1.0 / 3.0) );
534
535    // First check the Buerger conditions. Taken from: Gruber B.. Acta
536    // Cryst. A. 1973;29(4):433-440. Available at:
537    // http://scripts.iucr.org/cgi-bin/paper?S0567739473001063
538    // [Accessed December 15, 2010].
539    if (StableComp::gt(A, B, tol) || StableComp::gt(B, C, tol)) return false;
540    if (StableComp::eq(A, B, tol) && StableComp::gt(fabs(xi), fabs(eta), tol)) return false;
541    if (StableComp::eq(B, C, tol) && StableComp::gt(fabs(eta), fabs(zeta), tol)) return false;
542    if ( !(StableComp::gt(xi, 0.0, tol) &&
543           StableComp::gt(eta, 0.0, tol) &&
544           StableComp::gt(zeta, 0.0, tol))
545         &&
546         !(StableComp::leq(zeta, 0.0, tol) &&
547           StableComp::leq(zeta, 0.0, tol) &&
548           StableComp::leq(zeta, 0.0, tol)) ) return false;
549
550    // Check against Niggli conditions (taken from Gruber 1973). The
551    // logic of the second comparison is reversed from the paper to
552    // simplify the algorithm.
553    if (StableComp::eq(xi,    B, tol) && StableComp::gt (zeta, 2*eta,  tol)) return false;
554    if (StableComp::eq(eta,   A, tol) && StableComp::gt (zeta, 2*xi,   tol)) return false;
555    if (StableComp::eq(zeta,  A, tol) && StableComp::gt (eta,  2*xi,   tol)) return false;
556    if (StableComp::eq(xi,   -B, tol) && StableComp::neq(zeta, 0,      tol)) return false;
557    if (StableComp::eq(eta,  -A, tol) && StableComp::neq(zeta, 0,      tol)) return false;
558    if (StableComp::eq(zeta, -A, tol) && StableComp::neq(eta,  0,      tol)) return false;
559
560    if (StableComp::eq(xi+eta+zeta+A+B, 0, tol)
561        && StableComp::gt(2*(A+eta)+zeta,  0, tol)) return false;
562
563    // all good!
564    return true;
565  }
566
567  bool Xtal::fixAngles(int attempts)
568  {
569    // Perform niggli reduction
570    if (!niggliReduce(attempts)) {
571      qDebug() << "Unable to perform cell reduction on Xtal " << getIDString()
572               << "( " << getA() << getB() << getC()
573               << getAlpha() << getBeta() << getGamma() << " )";
574      return false;
575    }
576
577    findSpaceGroup();
578    return true;
579  }
580
581  bool Xtal::operator==(const Xtal &o) const
582  {
583    // Compare coordinates using the default tolerance
584    if (!compareCoordinates(o))
585      return false;
586
587    return true;
588  }
589
590  bool Xtal::compareCoordinates(const Xtal &other, const double lengthTol,
591                                const double angleTol) const
592  {
593    // Cell matrices as row vectors
594    const OpenBabel::matrix3x3 thisCellOB (this->cell()->GetCellMatrix());
595    const OpenBabel::matrix3x3 otherCellOB (other.cell()->GetCellMatrix());
596    XcMatrix thisCell (thisCellOB(0,0), thisCellOB(0,1), thisCellOB(0,2),
597                       thisCellOB(1,0), thisCellOB(1,1), thisCellOB(1,2),
598                       thisCellOB(2,0), thisCellOB(2,1), thisCellOB(2,2));
599    XcMatrix otherCell(otherCellOB(0,0), otherCellOB(0,1), otherCellOB(0,2),
600                       otherCellOB(1,0), otherCellOB(1,1), otherCellOB(1,2),
601                       otherCellOB(2,0), otherCellOB(2,1), otherCellOB(2,2));
602
603    // vectors of fractional coordinates and atomic numbers
604    std::vector<XcVector> thisCoords;
605    std::vector<XcVector> otherCoords;
606    std::vector<unsigned int> thisTypes;
607    std::vector<unsigned int> otherTypes;
608    thisCoords.reserve(this->numAtoms());
609    thisTypes.reserve(this->numAtoms());
610    otherCoords.reserve(other.numAtoms());
611    otherTypes.reserve(other.numAtoms());
612    Eigen::Vector3d pos;
613    for (QList<Atom*>::const_iterator it = this->m_atomList.constBegin(),
614           it_end = this->m_atomList.constEnd(); it != it_end; ++it) {
615      pos = this->cartToFrac(*(*it)->pos());
616      thisCoords.push_back(XcVector(pos.x(), pos.y(), pos.z()));
617      thisTypes.push_back((*it)->atomicNumber());
618    }
619    for (QList<Atom*>::const_iterator it = other.m_atomList.constBegin(),
620           it_end = other.m_atomList.constEnd(); it != it_end; ++it) {
621      pos = other.cartToFrac(*(*it)->pos());
622      otherCoords.push_back(XcVector(pos.x(), pos.y(), pos.z()));
623      otherTypes.push_back((*it)->atomicNumber());
624    }
625
626    return XtalComp::compare(thisCell,  thisTypes,  thisCoords,
627                             otherCell, otherTypes, otherCoords,
628                             NULL, lengthTol, angleTol);
629  }
630
631  OpenBabel::OBUnitCell* Xtal::cell() const
632  {
633    return OBUnitCell();
634  }
635
636  bool Xtal::addAtomRandomly(uint atomicNumber, double minIAD, double maxIAD, int maxAttempts, Atom **atom) {
637    INIT_RANDOM_GENERATOR();
638    Q_UNUSED(maxIAD);
639
640    double IAD = -1;
641    int i = 0;
642    vector3 cartCoords;
643
644    // For first atom, add to 0, 0, 0
645    if (numAtoms() == 0) {
646      cartCoords = vector3 (0,0,0);
647    }
648    else {
649      do {
650        // Generate fractional coordinates
651        IAD = -1;
652        double x = RANDDOUBLE();
653        double y = RANDDOUBLE();
654        double z = RANDDOUBLE();
655
656        // Convert to cartesian coordinates and store
657        vector3 fracCoords (x,y,z);
658        cartCoords = fracToCart(fracCoords);
659        if (minIAD != -1) {
660          getNearestNeighborDistance(cartCoords[0],
661                                     cartCoords[1],
662                                     cartCoords[2],
663                                     IAD);
664        }
665        else { break;};
666        i++;
667      } while (i < maxAttempts && IAD <= minIAD);
668
669      if (i >= maxAttempts) return false;
670    }
671    Atom *atm = addAtom();
672    atom = &atm;
673    Eigen::Vector3d pos (cartCoords[0],cartCoords[1],cartCoords[2]);
674    (*atom)->setPos(pos);
675    (*atom)->setAtomicNumber(static_cast<int>(atomicNumber));
676    return true;
677  }
678
679  bool Xtal::addAtomRandomly(
680      unsigned int atomicNumber,
681      const QHash<unsigned int, XtalCompositionStruct> & limits,
682      int maxAttempts, Avogadro::Atom **atom)
683  {
684    Eigen::Vector3d cartCoords;
685    bool success;
686
687    // For first atom, add to 0, 0, 0
688    if (numAtoms() == 0) {
689      cartCoords = Eigen::Vector3d (0,0,0);
690    }
691    else {
692      unsigned int i = 0;
693      vector3 fracCoords;
694
695      // Cache the minimum radius for the new atom
696      const double newMinRadius = limits.value(atomicNumber).minRadius;
697
698      // Compute a cut off distance -- atoms farther away than this value
699      // will abort the check early.
700      double maxCheckDistance = 0.0;
701      for (QHash<unsigned int, XtalCompositionStruct>::const_iterator
702           it = limits.constBegin(), it_end = limits.constEnd(); it != it_end;
703           ++it) {
704        if (it.value().minRadius > maxCheckDistance) {
705          maxCheckDistance = it.value().minRadius;
706        }
707      }
708      maxCheckDistance += newMinRadius;
709      const double maxCheckDistSquared = maxCheckDistance*maxCheckDistance;
710
711      do {
712        // Reset sentinal
713        success = true;
714
715        // Generate fractional coordinates
716        fracCoords.Set(RANDDOUBLE(), RANDDOUBLE(), RANDDOUBLE());
717
718        // Convert to cartesian coordinates and store
719        cartCoords = Eigen::Vector3d(this->fracToCart(fracCoords).AsArray());
720
721        // Compare distance to each atom in xtal with minimum radii
722        QVector<double> squaredDists;
723        this->getSquaredAtomicDistancesToPoint(cartCoords, &squaredDists);
724        Q_ASSERT_X(squaredDists.size() == this->numAtoms(), Q_FUNC_INFO,
725                   "Size of distance list does not match number of atoms.");
726
727        for (int dist_ind = 0; dist_ind < squaredDists.size(); ++dist_ind) {
728          const double &curDistSquared = squaredDists[dist_ind];
729          // Save a bit of time if distance is huge...
730          if (curDistSquared > maxCheckDistSquared) {
731            continue;
732          }
733          // Compare distance to minimum:
734          const double minDist = newMinRadius + limits.value(
735                this->atom(dist_ind)->atomicNumber()).minRadius;
736          const double minDistSquared = minDist * minDist;
737
738          if (curDistSquared < minDistSquared) {
739            success = false;
740            break;
741          }
742        }
743
744      } while (++i < maxAttempts && !success);
745
746      if (i >= maxAttempts) return false;
747    }
748    Atom *atm = addAtom();
749    atom = &atm;
750    (*atom)->setPos(cartCoords);
751    (*atom)->setAtomicNumber(static_cast<int>(atomicNumber));
752    return true;
753  }
754
755  bool Xtal::checkInteratomicDistances(
756      const QHash<unsigned int, XtalCompositionStruct> &limits,
757      int *atom1, int *atom2, double *IAD)
758  {
759      // Compute a cut off distance -- atoms farther away than this value
760      // will abort the check early.
761      double maxCheckDistance = 0.0;
762      for (QHash<unsigned int, XtalCompositionStruct>::const_iterator
763           it = limits.constBegin(), it_end = limits.constEnd(); it != it_end;
764           ++it) {
765        if (it.value().minRadius > maxCheckDistance) {
766          maxCheckDistance = it.value().minRadius;
767        }
768      }
769      maxCheckDistance += maxCheckDistance;
770      const double maxCheckDistSquared = maxCheckDistance*maxCheckDistance;
771
772      // Iterate through all of the atoms in the molecule for "a1"
773      for (QList<Atom*>::const_iterator a1 = m_atomList.constBegin(),
774           a1_end = m_atomList.constEnd(); a1 != a1_end; ++a1) {
775
776        // Get list of minimum squared distances between each atom and a1
777        QVector<double> squaredDists;
778        this->getSquaredAtomicDistancesToPoint(*(*a1)->pos(), &squaredDists);
779        Q_ASSERT_X(squaredDists.size() == this->numAtoms(), Q_FUNC_INFO,
780                   "Size of distance list does not match number of atoms.");
781
782        // Cache the minimum radius of a1
783        const double minA1Radius =
784            limits.value((*a1)->atomicNumber()).minRadius;
785
786        // Iterate through each distance
787        for (int i = 0; i < squaredDists.size(); ++i) {
788
789          // Grab the atom pointer at i, a2
790          Atom *a2 = this->atom(i);
791
792          // If a1 and a2 are the same, skip the comparison
793          if (*a1 == a2) {
794            continue;
795          }
796
797          // Cache the squared distance between a1 and a2
798          const double &curDistSquared = squaredDists[i];
799
800          // Skip comparison if the current distance exceeds the cutoff
801          if (curDistSquared > maxCheckDistSquared) {
802            continue;
803          }
804
805          // Calculate the minimum distance for the atom pair
806          const double minDist = limits.value(a2->atomicNumber()).minRadius
807              + minA1Radius;
808          const double minDistSquared = minDist * minDist;
809
810          // If the distance is too small, set atom1/atom2 and return false
811          if (curDistSquared < minDistSquared) {
812            if (atom1 != NULL && atom2 != NULL) {
813              *atom1 = m_atomList.indexOf(*a1);
814              *atom2 = m_atomList.indexOf(a2);
815              if (IAD != NULL) {
816                *IAD = sqrt(curDistSquared);
817              }
818            }
819            return false;
820          }
821
822          // Atom a2 is ok with respect to a1
823        }
824        // Atom a1 is ok will all a2
825      }
826      // all distances check out -- return true.
827      if (atom1 != NULL && atom2 != NULL) {
828        *atom1 = *atom2 = -1;
829        if (IAD != NULL) {
830          *IAD = 0.0;
831        }
832      }
833      return true;
834  }
835
836  bool Xtal::checkInteratomicDistances(
837      const QHash<unsigned int, XtalCompositionStruct> &limits,
838      const QList<Atom *> atoms, int *atom1, int *atom2, double *IAD)
839  {
840    // Compute a cut off distance -- atoms farther away than this value
841    // will abort the check early.
842    double maxCheckDistance = 0.0;
843    for (QHash<unsigned int, XtalCompositionStruct>::const_iterator
844         it = limits.constBegin(), it_end = limits.constEnd(); it != it_end;
845         ++it) {
846      if (it.value().minRadius > maxCheckDistance) {
847        maxCheckDistance = it.value().minRadius;
848      }
849    }
850    maxCheckDistance += maxCheckDistance;
851    const double maxCheckDistSquared = maxCheckDistance*maxCheckDistance;
852
853    // Iterate through all of the atoms in the list for "a1"
854    for (QList<Atom*>::const_iterator a1 = atoms.constBegin(),
855         a1_end = atoms.constEnd(); a1 != a1_end; ++a1) {
856
857      // Get list of minimum squared distances between each atom and a1
858      QVector<double> squaredDists;
859      this->getSquaredAtomicDistancesToPoint(*(*a1)->pos(), &squaredDists);
860      Q_ASSERT_X(squaredDists.size() == this->numAtoms(), Q_FUNC_INFO,
861                 "Size of distance list does not match number of atoms.");
862
863      // Cache the minimum radius of a1
864      const double minA1Radius =
865          limits.value((*a1)->atomicNumber()).minRadius;
866
867      // Iterate through each distance
868      for (int i = 0; i < squaredDists.size(); ++i) {
869
870        // Grab the atom pointer at i, a2
871        Atom *a2 = this->atom(i);
872
873        // Cache the squared distance between a1 and a2
874        const double &curDistSquared = squaredDists[i];
875
876        // Skip comparison if the current distance exceeds the cutoff
877        if (curDistSquared > maxCheckDistSquared) {
878          continue;
879        }
880
881        // Calculate the minimum distance for the atom pair
882        const double minDist = limits.value(a2->atomicNumber()).minRadius
883            + minA1Radius;
884        const double minDistSquared = minDist * minDist;
885
886        // If the distance is too small, set atom1/atom2 and return false
887        if (curDistSquared < minDistSquared) {
888          if (atom1 != NULL && atom2 != NULL) {
889            *atom1 = atoms.indexOf(*a1);
890            *atom2 = m_atomList.indexOf(a2);
891            if (IAD != NULL) {
892              *IAD = sqrt(curDistSquared);
893            }
894          }
895          return false;
896        }
897
898        // Atom a2 is ok with respect to a1
899      }
900      // Atom a1 is ok will all a2
901    }
902    // all distances check out -- return true.
903    if (atom1 != NULL && atom2 != NULL) {
904      *atom1 = *atom2 = -1;
905      if (IAD != NULL) {
906        *IAD = 0.0;
907      }
908    }
909    return true;
910  }
911
912
913  bool Xtal::getShortestInteratomicDistance(double & shortest) const {
914    QList<Atom*> atomList = atoms();
915    if (atomList.size() <= 1) return false; // Need at least two atoms!
916    QList<Vector3d> atomPositions;
917    for (int i = 0; i < atomList.size(); i++)
918      atomPositions.push_back(*(atomList.at(i)->pos()));
919
920    // Initialize vars
921    //  Atomic Positions
922    Vector3d v1= atomPositions.at(0);
923    Vector3d v2= atomPositions.at(1);
924    //  Unit Cell Vectors
925    //  First get OB matrix, extract vectors, then convert to Eigen::Vector3d's
926    matrix3x3 obcellMatrix = cell()->GetCellMatrix();
927    vector3 obU1 = obcellMatrix.GetRow(0);
928    vector3 obU2 = obcellMatrix.GetRow(1);
929    vector3 obU3 = obcellMatrix.GetRow(2);
930    Vector3d u1 (obU1.x(), obU1.y(), obU1.z());
931    Vector3d u2 (obU2.x(), obU2.y(), obU2.z());
932    Vector3d u3 (obU3.x(), obU3.y(), obU3.z());
933    //  Find all combinations of unit cell vectors to get wrapped neighbors
934    QList<Vector3d> uVecs;
935    int s_1, s_2, s_3; // will be -1, 0, +1 multipliers
936    for (s_1 = -1; s_1 <= 1; s_1++) {
937      for (s_2 = -1; s_2 <= 1; s_2++) {
938        for (s_3 = -1; s_3 <= 1; s_3++) {
939          uVecs.append(s_1*u1 + s_2*u2 + s_3*u3);
940        }
941      }
942    }
943
944    shortest = fabs((v1-v2).norm());
945    double distance;
946
947    // Find shortest distance
948    for (int i = 0; i < atomList.size(); i++) {
949      v1 = atomPositions.at(i);
950      for (int j = i+1; j < atomList.size(); j++) {
951        v2 = atomPositions.at(j);
952        // Intercell
953        distance = fabs((v1-v2).norm());
954        if (distance < shortest) shortest = distance;
955        // Intracell
956        for (int vecInd = 0; vecInd < uVecs.size(); vecInd++) {
957          distance = fabs(((v1+uVecs.at(vecInd))-v2).norm());
958          if (distance < shortest) shortest = distance;
959        }
960      }
961    }
962
963    return true;
964  }
965
966  bool Xtal::getSquaredAtomicDistancesToPoint(const Eigen::Vector3d &coord,
967                                              QVector<double> *distances)
968  {
969    int atmCount = this->numAtoms();
970    if (atmCount < 1) {
971      return false;
972    }
973
974    // Allocate memory
975    distances->resize(atmCount);
976
977    // Create list of all translation vectors to build a 3x3x3 supercell
978    //  First get OB matrix, extract vectors, then convert to Eigen::Vector3d's
979    matrix3x3 obcellMatrix = cell()->GetCellMatrix();
980    const Eigen::Vector3d u1 (obcellMatrix.GetRow(0).AsArray());
981    const Eigen::Vector3d u2 (obcellMatrix.GetRow(1).AsArray());
982    const Eigen::Vector3d u3 (obcellMatrix.GetRow(2).AsArray());
983    //  Find all combinations of unit cell vectors to get wrapped neighbors
984    QVector<Vector3d> uVecs;
985    uVecs.clear();
986    uVecs.reserve(27);
987    short s_1, s_2, s_3; // will be -1, 0, +1 multipliers
988    for (s_1 = -1; s_1 <= 1; ++s_1) {
989      for (s_2 = -1; s_2 <= 1; ++s_2) {
990        for (s_3 = -1; s_3 <= 1; ++s_3) {
991          uVecs.append(s_1*u1 + s_2*u2 + s_3*u3);
992        }
993      }
994    }
995
996    for (int i = 0; i < atmCount; ++i) {
997      const Eigen::Vector3d *pos = (this->atom(i)->pos());
998      double shortest = DBL_MAX;
999      for (QVector<Vector3d>::const_iterator it = uVecs.constBegin(),
1000           it_end = uVecs.constEnd(); it != it_end; ++it) {
1001        register double current = ((*it + *pos) - coord).squaredNorm();
1002        if (current < shortest) {
1003          shortest = current;
1004        }
1005      }
1006      (*distances)[i] = shortest;
1007    }
1008
1009    return true;
1010  }
1011
1012
1013  bool Xtal::getNearestNeighborDistance(const double x,
1014                                        const double y,
1015                                        const double z,
1016                                        double & shortest) const {
1017    if (this->numAtoms() < 1) {
1018      return false; // Need at least one atom!
1019    }
1020
1021    // Initialize vars
1022    //  Atomic Positions
1023    Vector3d v1 (x, y, z);
1024    const Vector3d *v2 = this->atom(0)->pos();
1025    //  Unit Cell Vectors
1026    //  First get OB matrix, extract vectors, then convert to Eigen::Vector3d's
1027    matrix3x3 obcellMatrix = cell()->GetCellMatrix();
1028    vector3 obU1 = obcellMatrix.GetRow(0);
1029    vector3 obU2 = obcellMatrix.GetRow(1);
1030    vector3 obU3 = obcellMatrix.GetRow(2);
1031    Vector3d u1 (obU1.x(), obU1.y(), obU1.z());
1032    Vector3d u2 (obU2.x(), obU2.y(), obU2.z());
1033    Vector3d u3 (obU3.x(), obU3.y(), obU3.z());
1034    //  Find all combinations of unit cell vectors to get wrapped neighbors
1035    QList<Vector3d> uVecs;
1036    int s_1, s_2, s_3; // will be -1, 0, +1 multipliers
1037    for (s_1 = -1; s_1 <= 1; s_1++) {
1038      for (s_2 = -1; s_2 <= 1; s_2++) {
1039        for (s_3 = -1; s_3 <= 1; s_3++) {
1040          uVecs.append(s_1*u1 + s_2*u2 + s_3*u3);
1041        }
1042      }
1043    }
1044
1045    shortest = fabs((v1 - (*v2) ).norm());
1046
1047    double distance;
1048
1049    // Find shortest distance
1050    for (int j = 0; j < this->numAtoms(); j++) {
1051      v2 = this->atom(j)->pos();
1052      // Intercell
1053      distance = fabs((v1 - (*v2)).norm());
1054      if (distance < shortest) shortest = distance;
1055      // Intracell
1056      for (int vecInd = 0; vecInd < uVecs.size(); vecInd++) {
1057        distance = fabs((((*v2)+uVecs.at(vecInd)) - v1).norm());
1058        if (distance < shortest) shortest = distance;
1059      }
1060    }
1061    return true;
1062  }
1063
1064  bool Xtal::getIADHistogram(QList<double> * distance,
1065                             QList<double> * frequency,
1066                             double min, double max, double step,
1067                             Atom *atom) const {
1068
1069    if (min > max && step > 0) {
1070      qWarning() << "Xtal::getIADHistogram: min cannot be greater than max!";
1071      return false;
1072    }
1073    if (step < 0 || step == 0) {
1074      qWarning() << "Xtal::getIADHistogram: invalid step size:" << step;
1075      return false;
1076    }
1077
1078    // Populate distance list
1079    distance->clear();
1080    frequency->clear();
1081    double val = min;
1082    do {
1083      distance->append(val);
1084      frequency->append(0);
1085      val += step;
1086    } while (val < max);
1087
1088    QList<Atom*> atomList = atoms();
1089    QList<Vector3d> atomPositions;
1090    for (int i = 0; i < atomList.size(); i++)
1091      atomPositions.push_back(*(atomList.at(i)->pos()));
1092
1093    // Initialize vars
1094    //  Atomic Positions
1095    Vector3d v1= atomPositions.at(0);
1096    Vector3d v2= atomPositions.at(1);
1097    //  Unit Cell Vectors
1098    //  First get OB matrix, extract vectors, then convert to Eigen::Vector3d's
1099    matrix3x3 obcellMatrix = cell()->GetCellMatrix();
1100    vector3 obU1 = obcellMatrix.GetRow(0);
1101    vector3 obU2 = obcellMatrix.GetRow(1);
1102    vector3 obU3 = obcellMatrix.GetRow(2);
1103    Vector3d u1 (obU1.x(), obU1.y(), obU1.z());
1104    Vector3d u2 (obU2.x(), obU2.y(), obU2.z());
1105    Vector3d u3 (obU3.x(), obU3.y(), obU3.z());
1106    //  Find all combinations of unit cell vectors to get wrapped neighbors
1107    QList<Vector3d> uVecs;
1108    int s_1, s_2, s_3; // will be -1, 0, +1 multipliers
1109    for (s_1 = -1; s_1 <= 1; s_1++) {
1110      for (s_2 = -1; s_2 <= 1; s_2++) {
1111        for (s_3 = -1; s_3 <= 1; s_3++) {
1112          uVecs.append(s_1*u1 + s_2*u2 + s_3*u3);
1113        }
1114      }
1115    }
1116    double diff;
1117
1118    // build histogram
1119    // Loop over all atoms
1120    if (atom == 0) {
1121      for (int i = 0; i < atomList.size(); i++) {
1122        v1 = atomPositions.at(i);
1123        for (int j = i+1; j < atomList.size(); j++) {
1124          v2 = atomPositions.at(j);
1125          // Intercell
1126          diff = fabs((v1-v2).norm());
1127          for (int k = 0; k < distance->size(); k++) {
1128            double radius = distance->at(k);
1129            if (fabs(diff-radius) < step/2) {
1130              (*frequency)[k]++;
1131            }
1132          }
1133          // Intracell
1134          for (int vecInd = 0; vecInd < uVecs.size(); vecInd++) {
1135            diff = fabs(((v1+uVecs.at(vecInd))-v2).norm());
1136            for (int k = 0; k < distance->size(); k++) {
1137              double radius = distance->at(k);
1138              if (fabs(diff-radius) < step/2) {
1139                (*frequency)[k]++;
1140              }
1141            }
1142          }
1143        }
1144      }
1145    }
1146    // Or, just the one requested
1147    else {
1148      v1 = *atom->pos();
1149      for (int j = 0; j < atomList.size(); j++) {
1150        v2 = atomPositions.at(j);
1151        // Intercell
1152        diff = fabs((v1-v2).norm());
1153        for (int k = 0; k < distance->size(); k++) {
1154          double radius = distance->at(k);
1155          if (diff != 0 && fabs(diff-radius) < step/2) {
1156            (*frequency)[k]++;
1157          }
1158        }
1159        // Intracell
1160        for (int vecInd = 0; vecInd < uVecs.size(); vecInd++) {
1161          diff = fabs(((v1+uVecs.at(vecInd))-v2).norm());
1162          for (int k = 0; k < distance->size(); k++) {
1163            double radius = distance->at(k);
1164            if (fabs(diff-radius) < step/2) {
1165              (*frequency)[k]++;
1166            }
1167          }
1168        }
1169      }
1170    }
1171
1172    return true;
1173  }
1174
1175  // Compare the symbols of two atoms to see which comes first alphabetically
1176  bool atomicSymbolSortLessThan(Atom *a1, Atom *a2)
1177  {
1178    // We know that the symbol is between 1-3 symbols long, so we can limit
1179    // the tests
1180    const char *s1 = OpenBabel::etab.GetSymbol(a1->atomicNumber());
1181    const char *s2 = OpenBabel::etab.GetSymbol(a2->atomicNumber());
1182    int ls1 = tolower(s1[0]);
1183    int ls2 = tolower(s2[0]);
1184    if (ls1 == ls2) {
1185      ls1 = tolower(s1[1]);
1186      ls2 = tolower(s2[1]);
1187      if (ls1 == ls2 && ls1 * ls2 != 0) {
1188        ls1 = tolower(s1[2]);
1189        ls2 = tolower(s2[2]);
1190      }
1191    }
1192    return (ls1 < ls2);
1193  }
1194
1195  QList<Atom*> Xtal::getAtomsSortedBySymbol() const
1196  {
1197    QList<Atom*> list = m_atomList;
1198    qSort(list.begin(), list.end(), atomicSymbolSortLessThan);
1199    return list;
1200  }
1201
1202  Eigen::Vector3d Xtal::fracToCart(const Eigen::Vector3d & fracCoords) const {
1203    OpenBabel::vector3 obfrac (fracCoords.x(), fracCoords.y(), fracCoords.z());
1204    OpenBabel::vector3 obcart = cell()->FractionalToCartesian(obfrac);
1205    Eigen::Vector3d cartCoords (obcart.x(), obcart.y(), obcart.z());
1206    return cartCoords;
1207  }
1208
1209  Eigen::Vector3d *Xtal::fracToCart(const Eigen::Vector3d *fracCoords) const {
1210    OpenBabel::vector3 obfrac (fracCoords->x(), fracCoords->y(), fracCoords->z());
1211    OpenBabel::vector3 obcart = cell()->FractionalToCartesian(obfrac);
1212    Eigen::Vector3d *cartCoords = new Eigen::Vector3d (obcart.x(), obcart.y(), obcart.z());
1213    return cartCoords;
1214  }
1215
1216  Eigen::Vector3d Xtal::cartToFrac(const Eigen::Vector3d & cartCoords) const {
1217    OpenBabel::vector3 obcart (cartCoords.x(), cartCoords.y(), cartCoords.z());
1218    OpenBabel::vector3 obfrac = cell()->CartesianToFractional(obcart);
1219    Eigen::Vector3d fracCoords (obfrac.x(), obfrac.y(), obfrac.z());
1220    return fracCoords;
1221  }
1222
1223  Eigen::Vector3d *Xtal::cartToFrac(const Eigen::Vector3d *cartCoords) const {
1224    OpenBabel::vector3 obcart (cartCoords->x(), cartCoords->y(), cartCoords->z());
1225    OpenBabel::vector3 obfrac = cell()->CartesianToFractional(obcart);
1226    Eigen::Vector3d *fracCoords = new Eigen::Vector3d (obfrac.x(), obfrac.y(), obfrac.z());
1227    return fracCoords;
1228  }
1229
1230  void Xtal::wrapAtomsToCell() {
1231    //qDebug() << "Xtal::wrapAtomsToCell() called";
1232    // Store position of atoms in fractional units
1233    QList<Atom*> atomList       = atoms();
1234    QList<Vector3d> fracCoordsList;
1235    for (int i = 0; i < atomList.size(); i++)
1236      fracCoordsList.append(cartToFrac(*(atomList.at(i)->pos())));
1237
1238    // wrap fractional coordinates to [0,1)
1239    for (int i = 0; i < fracCoordsList.size(); i++) {
1240      fracCoordsList[i][0] = fmod(fracCoordsList[i][0]+100, 1);
1241      fracCoordsList[i][1] = fmod(fracCoordsList[i][1]+100, 1);
1242      fracCoordsList[i][2] = fmod(fracCoordsList[i][2]+100, 1);
1243    }
1244
1245    // Recalculate cartesian coordinates:
1246    Eigen::Vector3d cartCoord;
1247    for (int i = 0; i < atomList.size(); i++) {
1248      cartCoord = fracToCart(fracCoordsList.at(i));
1249      atomList.at(i)->setPos(cartCoord);
1250    }
1251  }
1252
1253  QHash<QString, QVariant> Xtal::getFingerprint()
1254  {
1255    QHash<QString, QVariant> fp = Structure::getFingerprint();
1256    fp.insert("volume", getVolume());
1257    fp.insert("spacegroup", getSpaceGroupNumber());
1258    return fp;
1259  }
1260
1261  QString Xtal::getResultsEntry() const
1262  {
1263    QString status;
1264    switch (getStatus()) {
1265    case Optimized:
1266      status = "Optimized";
1267      break;
1268    case Killed:
1269    case Removed:
1270      status = "Killed";
1271      break;
1272    case Duplicate:
1273      status = "Duplicate";
1274      break;
1275    case Error:
1276      status = "Error";
1277      break;
1278    case Preoptimizing:
1279      status = "Preoptimizing";
1280      break;
1281    case StepOptimized:
1282    case WaitingForOptimization:
1283    case InProcess:
1284    case Empty:
1285    case Updating:
1286    case Restart:
1287    case Submitted:
1288      status = "In progress";
1289      break;
1290    default:
1291      status = "Unknown";
1292      break;
1293    }
1294    return QString("%1 %2 %3 %4 %5 %6")
1295      .arg(getRank(), 6)
1296      .arg(getGeneration(), 6)
1297      .arg(getIDNumber(), 6)
1298      .arg(getEnthalpy(), 10)
1299      .arg(m_spgSymbol, 10)
1300      .arg(status, 11);
1301  };
1302
1303
1304  uint Xtal::getSpaceGroupNumber() {
1305    if (m_spgNumber > 230)
1306      findSpaceGroup();
1307    return m_spgNumber;
1308  }
1309
1310  QString Xtal::getSpaceGroupSymbol() {
1311    if (m_spgNumber > 230)
1312      findSpaceGroup();
1313    return m_spgSymbol;
1314  }
1315
1316  QString Xtal::getHTMLSpaceGroupSymbol() {
1317    if (m_spgNumber > 230)
1318      findSpaceGroup();
1319
1320    QString s = m_spgSymbol;
1321
1322    // Prepare HTML tags
1323    s.prepend("<HTML>");
1324    s.append("</HTML>");
1325
1326    // "_X"  --> "<sub>X</sub>"
1327    int ind = s.indexOf("_");
1328    while (ind != -1) {
1329      s = s.insert(ind+2, "</sub>");
1330      s = s.replace(ind, 1, "<sub>");
1331      ind = s.indexOf("_");
1332    }
1333
1334    // "-X"  --> "<span style="text-decoration: overline">X</span>"
1335    ind = s.indexOf("-");
1336    while (ind != -1) {
1337      s = s.insert(ind+2, "</span>");
1338      s = s.replace(ind, 1, "<span style=\"text-decoration: overline\">");
1339      ind = s.indexOf("-", ind+35);
1340    }
1341
1342    return s;
1343  }
1344
1345  void Xtal::findSpaceGroup(double prec) {
1346    // Check that the precision is reasonable
1347    if (prec < 1e-5) {
1348      qWarning() << "Xtal::findSpaceGroup called with a precision of "
1349                 << prec << ". This is likely an error. Resetting prec to "
1350                 << 0.05 << ".";
1351      prec = 0.05;
1352    }
1353
1354    // reset space group to 0 so we can exit if needed
1355    m_spgNumber = 0;
1356    m_spgSymbol = "Unknown";
1357    int num = numAtoms();
1358
1359    // if no unit cell or atoms, exit
1360    if (!cell() || num == 0) {
1361      qWarning() << "Xtal::findSpaceGroup( " << prec << " ) called on atom with no cell or atoms!";
1362      return;
1363    }
1364
1365    // get lattice matrix
1366    std::vector<OpenBabel::vector3> vecs = cell()->GetCellVectors();
1367    vector3 obU1 = vecs[0];
1368    vector3 obU2 = vecs[1];
1369    vector3 obU3 = vecs[2];
1370    double lattice[3][3] = {
1371      {obU1.x(), obU2.x(), obU3.x()},
1372      {obU1.y(), obU2.y(), obU3.y()},
1373      {obU1.z(), obU2.z(), obU3.z()}
1374    };
1375
1376    // Get atom info
1377    double (*positions)[3] = new double[num][3];
1378    int *types = new int[num];
1379    QList<Atom*> atomList = atoms();
1380    Eigen::Vector3d fracCoords;
1381    for (int i = 0; i < atomList.size(); i++) {
1382      fracCoords        = cartToFrac(*(atomList.at(i)->pos()));
1383      types[i]          = atomList.at(i)->atomicNumber();
1384      positions[i][0]   = fracCoords.x();
1385      positions[i][1]   = fracCoords.y();
1386      positions[i][2]   = fracCoords.z();
1387    }
1388
1389    // find spacegroup
1390    char symbol[21];
1391    m_spgNumber = spg_get_international(symbol,
1392                                        lattice,
1393                                        positions,
1394                                        types,
1395                                        num, prec);
1396
1397    delete [] positions;
1398    delete [] types;
1399
1400    // Update the OBUnitCell object.
1401    cell()->SetSpaceGroup(m_spgNumber);
1402
1403    // Fail if m_spgNumber is still 0
1404    if (m_spgNumber == 0) {
1405      return;
1406    }
1407
1408    // Set and clean up the symbol string
1409    m_spgSymbol = QString(symbol);
1410    m_spgSymbol.remove(" ");
1411    return;
1412  }
1413
1414  void Xtal::getSpglibFormat() const {
1415    std::vector<OpenBabel::vector3> vecs = cell()->GetCellVectors();
1416    vector3 obU1 = vecs[0];
1417    vector3 obU2 = vecs[1];
1418    vector3 obU3 = vecs[2];
1419
1420    QString t;
1421    QTextStream out (&t);
1422
1423    out << "double lattice[3][3] = {\n"
1424        << "  {" << obU1.x() << ", " << obU2.x() << ", " << obU3.x() << "},\n"
1425        << "  {" << obU1.y() << ", " << obU2.y() << ", " << obU3.y() << "},\n"
1426        << "  {" << obU1.z() << ", " << obU2.z() << ", " << obU3.z() << "}};\n\n";
1427
1428    out << "double position[][3] = {";
1429    for (unsigned int i = 0; i < numAtoms(); i++) {
1430      if (i == 0 || i != numAtoms()-1) out << ",";
1431      out << "\n";
1432      out << "  {" << atom(i)->pos()->x() << ", "
1433          << atom(i)->pos()->y() << ", "
1434          << atom(i)->pos()->z() << "}";
1435    }
1436    out << "};\n\n";
1437    out << "int types[] = { ";
1438    for (unsigned int i = 0; i < numAtoms(); i++) {
1439      if (i == 0 || i != numAtoms()-1) out << ", ";
1440      out << atom(i)->atomicNumber();
1441    }
1442    out << " };\n";
1443    out << "int num_atom = " << numAtoms() << ";\n";
1444    qDebug() << t;
1445  }
1446
1447  bool Xtal::rotateCellToStandardOrientation()
1448  {
1449    // Get correct matrix
1450    Eigen::Matrix3d newMat
1451      (getCellMatrixInStandardOrientation());
1452
1453    // check that the matrix is valid
1454    if (newMat.isZero()) {
1455      const OpenBabel::matrix3x3 mat = cell()->GetCellMatrix();
1456      qDebug() << "Cannot rotate cell to std orientation:\n"
1457               << QString("%L1 %L2 %L3\n%L4 %L5 %L6\n%L7 %L8 %L9")
1458        .arg(mat(0,0), -9, 'g').arg(mat(0,1), -9, 'g').arg(mat(0,2), -9, 'g')
1459        .arg(mat(1,0), -9, 'g').arg(mat(1,1), -9, 'g').arg(mat(1,2), -9, 'g')
1460        .arg(mat(2,0), -9, 'g').arg(mat(2,1), -9, 'g').arg(mat(2,2), -9, 'g');
1461      return false;
1462    }
1463
1464    // Set the rotated basis
1465    setCellInfo(Eigen2OB(newMat));
1466
1467    return true;
1468  }
1469
1470  bool Xtal::rotateCellAndCoordsToStandardOrientation()
1471  {
1472    // Cache fractional coordinates
1473    QList<Eigen::Vector3d> fcoords;
1474    for (QList<Atom*>::const_iterator it = m_atomList.constBegin(),
1475           it_end = m_atomList.constEnd(); it != it_end; ++it) {
1476      fcoords.append( cartToFrac(*(*it)->pos()));
1477    }
1478
1479    if (!rotateCellToStandardOrientation()) {
1480      return false;
1481    }
1482
1483    // Reset coords
1484    Q_ASSERT(this->m_atomList.size() == fcoords.size());
1485    for (int i = 0; i < m_atomList.size(); ++i) {
1486      this->atom(i)->setPos(this->fracToCart(fcoords[i]));
1487    }
1488
1489    return true;
1490  }
1491
1492  Eigen::Matrix3d Xtal::getCellMatrixInStandardOrientation() const
1493  {
1494    // Cell matrix as row vectors
1495    const OpenBabel::matrix3x3 origRowMat = cell()->GetCellMatrix();
1496    return getCellMatrixInStandardOrientation(OB2Eigen(origRowMat));
1497  }
1498
1499  Eigen::Matrix3d Xtal::getCellMatrixInStandardOrientation
1500  (const Eigen::Matrix3d &origRowMat)
1501  {
1502    // Extract vector components:
1503    const double &x1 = origRowMat(0,0);
1504    const double &y1 = origRowMat(0,1);
1505    const double &z1 = origRowMat(0,2);
1506
1507    const double &x2 = origRowMat(1,0);
1508    const double &y2 = origRowMat(1,1);
1509    const double &z2 = origRowMat(1,2);
1510
1511    const double &x3 = origRowMat(2,0);
1512    const double &y3 = origRowMat(2,1);
1513    const double &z3 = origRowMat(2,2);
1514
1515    // Cache some frequently used values:
1516    // Length of v1
1517    const double L1 = sqrt(x1*x1 + y1*y1 + z1*z1);
1518    // Squared norm of v1's yz projection
1519    const double sqrdnorm1yz = y1*y1 + z1*z1;
1520    // Squared norm of v2's yz projection
1521    const double sqrdnorm2yz = y2*y2 + z2*z2;
1522    // Determinant of v1 and v2's projections in yz plane
1523    const double detv1v2yz = y2*z1 - y1*z2;
1524    // Scalar product of v1 and v2's projections in yz plane
1525    const double dotv1v2yz = y1*y2 + z1*z2;
1526
1527    // Used for denominators, since we want to check that they are
1528    // sufficiently far from 0 to keep things reasonable:
1529    double denom;
1530    const double DENOM_TOL = 1e-5;
1531
1532    // Create target matrix, fill with zeros
1533    Eigen::Matrix3d newMat (Eigen::Matrix3d::Zero());
1534
1535    // Set components of new v1:
1536    newMat(0,0) = L1;
1537
1538    // Set components of new v2:
1539    denom = L1;
1540    if (fabs(denom) < DENOM_TOL) {
1541      return Eigen::Matrix3d::Zero();
1542    };
1543    newMat(1,0) = (x1*x2 + y1*y2 + z1*z2) / denom;
1544
1545    newMat(1,1) = sqrt(x2*x2 * sqrdnorm1yz +
1546                       detv1v2yz*detv1v2yz -
1547                       2*x1*x2*dotv1v2yz +
1548                       x1*x1*sqrdnorm2yz) / denom;
1549
1550    // Set components of new v3
1551    // denom is still L1
1552    Q_ASSERT(denom == L1);
1553    newMat(2,0) = (x1*x3 + y1*y3 + z1*z3) / denom;
1554
1555    denom = L1*L1 * newMat(1,1);
1556    if (fabs(denom) < DENOM_TOL) {
1557      return Eigen::Matrix3d::Zero();
1558    };
1559    newMat(2,1) = (x1*x1*(y2*y3 + z2*z3) +
1560                   x2*(x3*sqrdnorm1yz -
1561                       x1*(y1*y3 + z1*z3)
1562                       ) +
1563                   detv1v2yz*(y3*z1 - y1*z3) -
1564                   x1*x3*dotv1v2yz) / denom;
1565
1566    denom = L1 * newMat(1,1);
1567    if (fabs(denom) < DENOM_TOL) {
1568      return Eigen::Matrix3d::Zero();
1569    };
1570    // Numerator is determinant of original cell:
1571    newMat(2,2) = (x1*y2*z3 - x1*y3*z2 +
1572                   x2*y3*z1 - x2*y1*z3 +
1573                   x3*y1*z2 - x3*y2*z1) / denom;
1574
1575    return newMat;
1576  }
1577
1578  // Initialize static members for COB list generation
1579  QMutex Xtal::m_validCOBsGenMutex;
1580  QVector<Eigen::Matrix3d> Xtal::m_transformationMatrices;
1581  QVector<Eigen::Matrix3d> Xtal::m_mixMatrices;
1582
1583  static inline bool COBIsValid(const Eigen::Matrix3d &cob)
1584  {
1585    // determinant must be +/- 1
1586    if (fabs(fabs(cob.determinant()) - 1.0) < 1e-4)
1587      return false;
1588
1589    return true;
1590  }
1591
1592  void Xtal::generateValidCOBs()
1593  {
1594    m_validCOBsGenMutex.lock();
1595
1596    // Has another instance beat us to the punch?
1597    if (m_mixMatrices.size()) {
1598      m_validCOBsGenMutex.unlock();
1599      return;
1600    }
1601
1602    Eigen::Matrix3d tmpMat;
1603
1604    m_transformationMatrices.clear();
1605    m_transformationMatrices.reserve(32);
1606    m_mixMatrices.clear();
1607    m_mixMatrices.reserve(8);
1608
1609    // Build list of transformation matrices
1610    // First build list of 90 degree rotations
1611    tmpMat <<  1, 0, 0,   0, 1, 0,   0, 0, 1; m_transformationMatrices<<tmpMat;
1612    tmpMat <<  1, 0, 0,   0, 0, 1,   0, 1, 0; m_transformationMatrices<<tmpMat;
1613    tmpMat <<  0, 1, 0,   1, 0, 0,   0, 0, 1; m_transformationMatrices<<tmpMat;
1614    tmpMat <<  0, 0, 1,   0, 1, 0,   1, 0, 0; m_transformationMatrices<<tmpMat;
1615    for (unsigned short int i = 0; i < 4; ++i) {
1616      // Now apply all possible reflections to 90 rotations
1617      tmpMat <<-1, 0, 0,   0, 1, 0,   0, 0, 1;
1618      m_transformationMatrices << (tmpMat * m_transformationMatrices[i]);
1619      tmpMat << 1, 0, 0,   0,-1, 0,   0, 0, 1;
1620      m_transformationMatrices << (tmpMat * m_transformationMatrices[i]);
1621      tmpMat << 1, 0, 0,   0, 1, 0,   0, 0,-1;
1622      m_transformationMatrices << (tmpMat * m_transformationMatrices[i]);
1623      tmpMat <<-1, 0, 0,   0,-1, 0,   0, 0, 1;
1624      m_transformationMatrices << (tmpMat * m_transformationMatrices[i]);
1625      tmpMat <<-1, 0, 0,   0, 1, 0,   0, 0,-1;
1626      m_transformationMatrices << (tmpMat * m_transformationMatrices[i]);
1627      tmpMat << 1, 0, 0,   0,-1, 0,   0, 0,-1;
1628      m_transformationMatrices << (tmpMat * m_transformationMatrices[i]);
1629      tmpMat <<-1, 0, 0,   0,-1, 0,   0, 0,-1;
1630      m_transformationMatrices << (tmpMat * m_transformationMatrices[i]);
1631    }
1632
1633    // Now build list of mix matrices
1634    // Identity
1635    tmpMat <<  1, 0, 0,   0, 1, 0,   0, 0, 1; m_mixMatrices.append(tmpMat);
1636    // Upper triangular mixes
1637    tmpMat <<  1, 1, 0,   0, 1, 0,   0, 0, 1; m_mixMatrices.append(tmpMat);
1638    tmpMat <<  1, 1, 1,   0, 1, 0,   0, 0, 1; m_mixMatrices.append(tmpMat);
1639    tmpMat <<  1, 1, 0,   0, 1, 1,   0, 0, 1; m_mixMatrices.append(tmpMat);
1640    tmpMat <<  1, 1, 1,   0, 1, 1,   0, 0, 1; m_mixMatrices.append(tmpMat);
1641    tmpMat <<  1, 0, 1,   0, 1, 0,   0, 0, 1; m_mixMatrices.append(tmpMat);
1642    tmpMat <<  1, 0, 1,   0, 1, 1,   0, 0, 1; m_mixMatrices.append(tmpMat);
1643    tmpMat <<  1, 0, 0,   0, 1, 1,   0, 0, 1; m_mixMatrices.append(tmpMat);
1644
1645    m_validCOBsGenMutex.unlock();
1646  }
1647
1648  Xtal * Xtal::getRandomRepresentation() const
1649  {
1650    // Cache volume for later sanity checks
1651    const double origVolume = cell()->GetCellVolume();
1652
1653    // Randomly select a mix matrix to create a new cell matrix by
1654    // taking a linear combination of the current cell vectors
1655    const Eigen::Matrix3d &mix
1656      (m_mixMatrices[RANDUINT() % m_mixMatrices.size()]);
1657
1658    // Build new Xtal with the new basis
1659    Xtal *nxtal = new Xtal (this->parent());
1660    nxtal->setCellInfo(Eigen2OB(mix) * this->cell()->GetCellMatrix());
1661
1662    Q_ASSERT_X(StableComp::eq(origVolume, nxtal->cell()->GetCellVolume()),
1663               Q_FUNC_INFO, "Randomized cell volume not "
1664               "equal to original structure.");
1665
1666    // Generate a random translation (i.e. between 0 and 1)
1667    const double maxTranslation = getA() + getB() + getC();
1668    const Eigen::Vector3d randTranslation
1669      (RANDDOUBLE() * maxTranslation,
1670       RANDDOUBLE() * maxTranslation,
1671       RANDDOUBLE() * maxTranslation);
1672
1673    // Add atoms
1674    for (QList<Atom*>::const_iterator it = m_atomList.constBegin(),
1675           it_end = m_atomList.constEnd(); it != it_end; ++it) {
1676      Atom * atom = nxtal->addAtom();
1677      atom->setAtomicNumber((*it)->atomicNumber());
1678      atom->setPos( (*(*it)->pos()) + randTranslation);
1679    }
1680
1681    // rotate and wrap:
1682    nxtal->rotateCellAndCoordsToStandardOrientation();
1683    nxtal->wrapAtomsToCell();
1684    return nxtal;
1685  }
1686
1687  Xtal* Xtal::POSCARToXtal(const QString &poscar)
1688  {
1689    QTextStream ps (&const_cast<QString &>(poscar));
1690    QStringList sl;
1691    vector3 v1, v2, v3, pos;
1692    Xtal *xtal = new Xtal;
1693
1694    ps.readLine(); // title
1695    float scale = ps.readLine().toFloat(); // Scale factor
1696    sl = ps.readLine().split(QRegExp("\\s+"), QString::SkipEmptyParts); // v1
1697    v1.x() = sl.at(0).toFloat() * scale;
1698    v1.y() = sl.at(1).toFloat() * scale;
1699    v1.z() = sl.at(2).toFloat() * scale;
1700
1701    sl = ps.readLine().split(QRegExp("\\s+"), QString::SkipEmptyParts); // v2
1702    v2.x() = sl.at(0).toFloat() * scale;
1703    v2.y() = sl.at(1).toFloat() * scale;
1704    v2.z() = sl.at(2).toFloat() * scale;
1705
1706    sl = ps.readLine().split(QRegExp("\\s+"), QString::SkipEmptyParts); // v3
1707    v3.x() = sl.at(0).toFloat() * scale;
1708    v3.y() = sl.at(1).toFloat() * scale;
1709    v3.z() = sl.at(2).toFloat() * scale;
1710
1711    xtal->setCellInfo(v1, v2, v3);
1712
1713    sl = ps.readLine().split(QRegExp("\\s+"), QString::SkipEmptyParts); // atom types
1714    unsigned int numAtomTypes = sl.size();
1715    QList<unsigned int> atomCounts;
1716    for (int i = 0; i < numAtomTypes; i++) {
1717      atomCounts.append(sl.at(i).toUInt());
1718    }
1719
1720    // TODO this will assume fractional coordinates. VASP can use cartesian!
1721    ps.readLine(); // direct or cartesian
1722    // Atom coords begin
1723    Atom *atom;
1724    for (unsigned int i = 0; i < numAtomTypes; i++) {
1725      for (unsigned int j = 0; j < atomCounts.at(i); j++) {
1726        // Actual identity of the atoms doesn't matter for the symmetry
1727        // test. Just use (i+1) as the atomic number.
1728        atom = xtal->addAtom();
1729        atom->setAtomicNumber(i+1);
1730        // Get coords
1731        sl = ps.readLine().split(QRegExp("\\s+"), QString::SkipEmptyParts); // coords
1732        Eigen::Vector3d pos;
1733        pos.x() = sl.at(0).toDouble();
1734        pos.y() = sl.at(1).toDouble();
1735        pos.z() = sl.at(2).toDouble();
1736        atom->setPos(xtal->fracToCart(pos));
1737      }
1738    }
1739
1740    return xtal;
1741  }
1742
1743  Xtal* Xtal::POSCARToXtal(QFile *file)
1744  {
1745    QString poscar;
1746    file->open(QFile::ReadOnly);
1747    poscar = file->readAll();
1748    file->close();
1749    return POSCARToXtal(poscar);
1750  }
1751
1752  void Xtal::shortenFractionalVector(Eigen::Vector3d *fracVec)
1753  {
1754    while (fracVec->x() >  0.5) --fracVec->x();
1755    while (fracVec->x() < -0.5) ++fracVec->x();
1756    while (fracVec->y() >  0.5) --fracVec->y();
1757    while (fracVec->y() < -0.5) ++fracVec->y();
1758    while (fracVec->z() >  0.5) --fracVec->z();
1759    while (fracVec->z() < -0.5) ++fracVec->z();
1760  }
1761
1762} // end namespace XtalOpt
Note: See TracBrowser for help on using the browser.