#include <openbabel/babelconfig.h>
#include <openbabel/obmolecformat.h>
#include <openbabel/mol.h>
#include <openbabel/atom.h>
#include <openbabel/bond.h>
#include <openbabel/obiter.h>
#include <openbabel/elements.h>
#include <openbabel/generic.h>
#include <limits>
#include <locale>
#include <functional>
#include <iostream>
#include <algorithm>
#include <cstdlib>
#define EV_TO_KCAL_PER_MOL 23.060538
using namespace std;
namespace OpenBabel {
class VASPFormat : public OBMoleculeFormat
{
protected:
class compare_sort_items
{
std::vector<int> csm;
bool num_sort;
public:
compare_sort_items(const std::vector<int> &_custom_sort_nums, bool _num_sort):
csm(_custom_sort_nums), num_sort(_num_sort) {};
bool operator()(const OBAtom *a, const OBAtom *b)
{
int a_num = a->GetAtomicNum();
int b_num = b->GetAtomicNum();
int dist = std::distance(std::find(csm.begin(), csm.end(), b_num),
std::find(csm.begin(), csm.end(), a_num));
if ( dist != 0)
return dist < 0;
if( (num_sort) && ( a_num - b_num != 0 ) )
return a_num < b_num;
return false;
}
};
public:
VASPFormat()
{
OBConversion::RegisterFormat("CONTCAR",this);
OBConversion::RegisterFormat("POSCAR",this);
OBConversion::RegisterFormat("VASP",this);
OBConversion::RegisterOptionParam("s", this, 0, OBConversion::INOPTIONS);
OBConversion::RegisterOptionParam("b", this, 0, OBConversion::INOPTIONS);
OBConversion::RegisterOptionParam("w", this, 0, OBConversion::OUTOPTIONS);
OBConversion::RegisterOptionParam("z", this, 0, OBConversion::OUTOPTIONS);
OBConversion::RegisterOptionParam("4", this, 0, OBConversion::OUTOPTIONS);
}
virtual const char* Description()
{
return
"VASP format\n"
"Reads in data from POSCAR and CONTCAR to obtain information from "
"VASP calculations.\n\n"
"Due to limitations in Open Babel's file handling, reading in VASP\n"
"files can be a bit tricky; the client that is using Open Babel must\n"
"use OBConversion::ReadFile() to begin the conversion. This change is\n"
"usually trivial. Also, the complete path to the CONTCAR/POSCAR file\n"
"must be provided, otherwise the other files needed will not be\n"
"found.\n\n"
"Both VASP 4.x and 5.x POSCAR formats are supported.\n\n"
"By default, atoms are written out in the order they are present in the input\n"
"molecule. To sort by atomic number specify ``-xw``. To specify the sort\n"
"order, use the ``-xz`` option.\n\n"
"Read Options e.g. -as\n"
" s Output single bonds only\n"
" b Disable bonding entirely\n\n"
"Write Options e.g. -x4\n"
" w Sort atoms by atomic number\n"
" z <list of atoms> Specify the order to write out atoms\n"
" 'atom1 atom2 ...': atom1 first, atom2 second, etc. The remaining\n"
" atoms are written in the default order or (if ``-xw`` is specified)\n"
" in order of atomic number.\n"
" 4 Write a POSCAR using the VASP 4.x specification.\n"
" The default is to use the VASP 5.x specification.\n\n"
;
};
virtual const char* SpecificationURL(){return "http://cms.mpi.univie.ac.at/vasp/vasp/vasp.html";};
virtual unsigned int Flags()
{
return READONEONLY;
};
virtual int SkipObjects(int n, OBConversion* pConv)
{
return 0;
};
virtual bool ReadMolecule(OBBase* pOb, OBConversion* pConv);
virtual bool WriteMolecule(OBBase* pOb, OBConversion* pConv);
private:
};
VASPFormat theVASPFormat;
bool VASPFormat::ReadMolecule(OBBase* pOb, OBConversion* pConv)
{
OBMol* pmol = pOb->CastAndClear<OBMol>();
if (pmol == nullptr)
return false;
istream &ifs = *pConv->GetInStream();
ifs.seekg (0, ios::end);
char buffer[BUFF_SIZE], tag[BUFF_SIZE];
double x,y,z, scale;
unsigned int totalAtoms=0, atomIndex=0, atomCount=0;
OBAtom *atom;
bool cartesian;
string str, path;
vector<string> vs;
vector<unsigned int> numAtoms, atomTypes;
bool selective; string key, value; OBPairData *cp; bool hasEnthalpy=false;
bool hasVibrations=false;
bool needSymbolsInGeometryFile = false;
double enthalpy_eV, pv_eV;
vector<vector <vector3> > Lx;
vector<double> Frequencies;
vector<matrix3x3> dipGrad;
path = pConv->GetInFilename();
if (path.empty()) return false; size_t found;
found = path.rfind("/");
path = path.substr(0, found);
if (found == string::npos) path = "./";
string potcar_filename = path + "/POTCAR";
string outcar_filename = path + "/OUTCAR";
string doscar_filename = path + "/DOSCAR";
string contcar_filename = pConv->GetInFilename(); ifstream ifs_pot (potcar_filename.c_str());
ifstream ifs_out (outcar_filename.c_str());
ifstream ifs_dos (doscar_filename.c_str());
ifstream ifs_cont (contcar_filename.c_str());
if (!ifs_pot) {
needSymbolsInGeometryFile = true;
}
if (!ifs_cont) {
return false; }
pmol->BeginModify();
ifs_cont.getline(buffer,BUFF_SIZE); pmol->SetTitle(buffer);
ifs_cont.getline(buffer,BUFF_SIZE); scale = atof(buffer);
ifs_cont.getline(buffer,BUFF_SIZE); tokenize(vs, buffer);
x = atof(vs.at(0).c_str()) * scale;
y = atof(vs.at(1).c_str()) * scale;
z = atof(vs.at(2).c_str()) * scale;
vector3 x_vec (x,y,z);
ifs_cont.getline(buffer,BUFF_SIZE); tokenize(vs, buffer);
x = atof(vs.at(0).c_str()) * scale;
y = atof(vs.at(1).c_str()) * scale;
z = atof(vs.at(2).c_str()) * scale;
vector3 y_vec (x,y,z);
ifs_cont.getline(buffer,BUFF_SIZE); tokenize(vs, buffer);
x = atof(vs.at(0).c_str()) * scale;
y = atof(vs.at(1).c_str()) * scale;
z = atof(vs.at(2).c_str()) * scale;
vector3 z_vec (x,y,z);
OBUnitCell *cell = new OBUnitCell;
cell->SetData(x_vec, y_vec, z_vec);
cell->SetSpaceGroup(1);
pmol->SetData(cell);
ifs_cont.getline(buffer,BUFF_SIZE);
tokenize(vs, buffer);
bool symbolsInGeometryFile = false;
if (vs.size() != 0) {
if (vs.at(0).size() != 0) {
if (isalpha(static_cast<int>(vs.at(0).at(0))) != 0) {
symbolsInGeometryFile = true;
}
}
}
if (needSymbolsInGeometryFile && !symbolsInGeometryFile &&
vs.size() != 0) {
pmol->EndModify();
return false;
}
if (symbolsInGeometryFile) {
atomTypes.clear();
for (size_t i = 0; i < vs.size(); ++i) {
atomTypes.push_back(OpenBabel::OBElements::GetAtomicNum(vs.at(i).c_str()));
}
ifs_cont.getline(buffer,BUFF_SIZE);
tokenize(vs, buffer);
}
else if (ifs_pot) {
while (ifs_pot.getline(buffer,BUFF_SIZE)) {
if (strstr(buffer,"VRHFIN")) {
str = buffer;
size_t start = str.find("=") + 1;
size_t end = str.find(":");
str = str.substr(start, end - start);
for (unsigned int i = 0; i < str.size(); i++)
if (str.at(i) == ' ') {
str.erase(i,1);
--i;
}
atomTypes.push_back(OpenBabel::OBElements::GetAtomicNum(str.c_str()));
}
}
ifs_pot.close();
}
totalAtoms = 0;
for (unsigned int i = 0; i < vs.size(); i++) {
int currentCount = atoi(vs.at(i).c_str());
numAtoms.push_back(currentCount);
totalAtoms += currentCount;
}
if (numAtoms.size() != atomTypes.size()) {
pmol->EndModify();
return false;
}
ifs_cont.getline(buffer,BUFF_SIZE);
selective = false;
if (buffer[0] == 'S' || buffer[0] == 's') {
selective = true;
ifs_cont.getline(buffer,BUFF_SIZE);
}
if ( buffer[0] == 'C' || buffer[0] == 'c' ||
buffer[0] == 'K' || buffer[0] == 'k' ) {
cartesian = true;
}
else {
cartesian = false;
}
atomCount = 0;
for (unsigned int i = 0; i < totalAtoms; i++) {
ifs_cont.getline(buffer,BUFF_SIZE);
while (atomCount >= numAtoms.at(atomIndex)) {
atomIndex++;
atomCount = 0;
}
tokenize(vs, buffer);
atom = pmol->NewAtom();
atom->SetAtomicNum(atomTypes.at(atomIndex));
x = atof((char*)vs[0].c_str());
y = atof((char*)vs[1].c_str());
z = atof((char*)vs[2].c_str());
vector3 coords (x,y,z);
if (!cartesian)
coords = cell->FractionalToCartesian( coords );
else coords *= scale;
atom->SetVector(coords);
if (selective && vs.size() >= 6) {
key = "move";
value = " "; value += vs[3].c_str();
value += " "; value += vs[4].c_str();
value += " "; value += vs[5].c_str();
cp = new OBPairData;
cp->SetAttribute(key);
cp->SetValue(value);
cp->SetOrigin(fileformatInput);
atom->SetData(cp);
}
atomCount++;
};
ifs_cont.close();
if (ifs_dos) {
OBDOSData *dos = new OBDOSData();
ifs_dos.getline(buffer,BUFF_SIZE); ifs_dos.getline(buffer,BUFF_SIZE); ifs_dos.getline(buffer,BUFF_SIZE); ifs_dos.getline(buffer,BUFF_SIZE); ifs_dos.getline(buffer,BUFF_SIZE);
double fermi;
if (ifs_dos.getline(buffer,BUFF_SIZE)) { tokenize(vs, buffer);
fermi = atof(vs[3].c_str());
}
std::vector<double> energies;
std::vector<double> densities;
std::vector<double> integration;
while (ifs_dos.getline(buffer,BUFF_SIZE)) {
tokenize(vs, buffer);
energies.push_back(atof(vs[0].c_str()));
densities.push_back(atof(vs[1].c_str()));
integration.push_back(atof(vs[2].c_str()));
}
if (energies.size() != 0) {
dos->SetData(fermi, energies, densities, integration);
pmol->SetData(dos);
}
}
ifs_dos.close();
vector3 prevDm;
vector<vector3> prevXyz;
vector3 currDm;
vector<vector3> currXyz;
if (ifs_out) {
while (ifs_out.getline(buffer,BUFF_SIZE)) {
if (strstr(buffer, "enthalpy is")) {
hasEnthalpy = true;
tokenize(vs, buffer);
enthalpy_eV = atof(vs[4].c_str());
pv_eV = atof(vs[8].c_str());
}
if (strstr(buffer, "free energy")) {
tokenize(vs, buffer);
pmol->SetEnergy(atof(vs[4].c_str()) * EV_TO_KCAL_PER_MOL);
}
if (strstr(buffer, "Eigenvectors") && Frequencies.size() == 0) {
hasVibrations = true;
double x, y, z;
ifs_out.getline(buffer,BUFF_SIZE); ifs_out.getline(buffer,BUFF_SIZE); ifs_out.getline(buffer,BUFF_SIZE); ifs_out.getline(buffer,BUFF_SIZE); while (!strstr(buffer, "Eigenvectors")) {
vector<vector3> vib;
tokenize(vs, buffer);
int freqnum = atoi(vs[0].c_str());
if (vs[1].size() == 1 and vs[1].compare("f") == 0) {
Frequencies.push_back(atof(vs[7].c_str()));
} else if (strstr(vs[1].c_str(), "f/i=")) {
Frequencies.push_back(-atof(vs[6].c_str()));
} else {
break;
}
ifs_out.getline(buffer,BUFF_SIZE); ifs_out.getline(buffer,BUFF_SIZE); tokenize(vs, buffer);
while (vs.size() == 6) {
x = atof(vs[3].c_str());
y = atof(vs[4].c_str());
z = atof(vs[5].c_str());
vib.push_back(vector3(x, y, z));
ifs_out.getline(buffer,BUFF_SIZE); tokenize(vs, buffer);
}
Lx.push_back(vib);
ifs_out.getline(buffer,BUFF_SIZE); }
}
if (strstr(buffer, "dipolmoment")) {
tokenize(vs, buffer);
x = atof(vs[1].c_str());
y = atof(vs[2].c_str());
z = atof(vs[3].c_str());
currDm.Set(x, y, z);
}
if (strstr(buffer, "TOTAL-FORCE")) {
currXyz.clear();
ifs_out.getline(buffer, BUFF_SIZE); ifs_out.getline(buffer, BUFF_SIZE);
tokenize(vs, buffer);
while (vs.size() == 6) {
x = atof(vs[0].c_str());
y = atof(vs[1].c_str());
z = atof(vs[2].c_str());
currXyz.push_back(vector3(x, y, z));
ifs_out.getline(buffer, BUFF_SIZE); tokenize(vs, buffer);
}
}
if (strstr(buffer, "BORN EFFECTIVE CHARGES")) {
dipGrad.clear();
ifs_out.getline(buffer, BUFF_SIZE); ifs_out.getline(buffer, BUFF_SIZE); tokenize(vs, buffer);
while (vs.size() == 2) {
matrix3x3 dmudq;
for (int row = 0; row < 3; ++row) {
ifs_out.getline(buffer, BUFF_SIZE);
tokenize(vs, buffer);
x = atof(vs[1].c_str());
y = atof(vs[2].c_str());
z = atof(vs[3].c_str());
dmudq.SetRow(row, vector3(x, y, z));
}
dipGrad.push_back(dmudq);
ifs_out.getline(buffer, BUFF_SIZE); tokenize(vs, buffer);
}
} else if (strstr(buffer, "free energy")) {
if (dipGrad.empty()) {
dipGrad.resize(pmol->NumAtoms());
} else if (prevXyz.empty()) {
prevXyz = currXyz;
prevDm = currDm;
} else {
for (size_t natom = 0; natom < pmol->NumAtoms(); ++natom) {
const vector3 dxyz = currXyz[natom] - prevXyz[natom];
vector3::const_iterator iter = std::find_if(dxyz.begin(), dxyz.end(),
std::bind2nd(std::not_equal_to<double>(), 0.0));
if (iter != dxyz.end()) dipGrad[natom].SetRow(iter - dxyz.begin(),
(currDm - prevDm) / *iter);
}
prevXyz.clear();
}
}
}
}
ifs_out.close();
if (hasEnthalpy) {
OBPairData *enthalpyPD = new OBPairData();
OBPairData *enthalpyPD_pv = new OBPairData();
OBPairData *enthalpyPD_eV = new OBPairData();
OBPairData *enthalpyPD_pv_eV = new OBPairData();
enthalpyPD->SetAttribute("Enthalpy (kcal/mol)");
enthalpyPD_pv->SetAttribute("Enthalpy PV term (kcal/mol)");
enthalpyPD_eV->SetAttribute("Enthalpy (eV)");
enthalpyPD_pv_eV->SetAttribute("Enthalpy PV term (eV)");
double en_kcal_per_mole = enthalpy_eV * EV_TO_KCAL_PER_MOL;
double pv_kcal_per_mole = pv_eV * EV_TO_KCAL_PER_MOL;
snprintf(tag, BUFF_SIZE, "%f", en_kcal_per_mole);
enthalpyPD->SetValue(tag);
snprintf(tag, BUFF_SIZE, "%f", pv_kcal_per_mole);
enthalpyPD_pv->SetValue(tag);
snprintf(tag, BUFF_SIZE, "%f", enthalpy_eV);
enthalpyPD_eV->SetValue(tag);
snprintf(tag, BUFF_SIZE, "%f", pv_eV);
enthalpyPD_pv_eV->SetValue(tag);
pmol->SetData(enthalpyPD);
pmol->SetData(enthalpyPD_pv);
pmol->SetData(enthalpyPD_eV);
pmol->SetData(enthalpyPD_pv_eV);
}
if (hasVibrations) {
vector<double> Intensities;
for (vector<vector<vector3> >::const_iterator
lxIter = Lx.begin(); lxIter != Lx.end(); ++lxIter) {
vector3 intensity;
for (size_t natom = 0; natom < dipGrad.size(); ++natom) {
intensity += dipGrad[natom].transpose() * lxIter->at(natom)
/ sqrt(pmol->GetAtomById(natom)->GetAtomicMass());
}
Intensities.push_back(dot(intensity, intensity));
}
const double max = *max_element(Intensities.begin(), Intensities.end());
if (max != 0.0) {
std::transform(Intensities.begin(), Intensities.end(), Intensities.begin(),
std::bind2nd(std::divides<double>(), max / 100.0));
} else {
Intensities.clear();
}
OBVibrationData* vd = new OBVibrationData;
vd->SetData(Lx, Frequencies, Intensities);
pmol->SetData(vd);
}
pmol->EndModify();
const char *noBonding = pConv->IsOption("b", OBConversion::INOPTIONS);
const char *singleOnly = pConv->IsOption("s", OBConversion::INOPTIONS);
if (noBonding == nullptr) {
pmol->ConnectTheDots();
if (singleOnly == nullptr) {
pmol->PerceiveBondOrders();
}
}
return true;
}
bool VASPFormat::WriteMolecule(OBBase* pOb, OBConversion* pConv)
{
OBMol* pmol = dynamic_cast<OBMol*>(pOb);
if (pmol == nullptr) {
return false;
}
ostream& ofs = *pConv->GetOutStream();
OBMol &mol = *pmol;
char buffer[BUFF_SIZE];
OBUnitCell *uc = nullptr;
vector<vector3> cell;
const char * sortAtomsNum = pConv->IsOption("w", OBConversion::OUTOPTIONS);
const char * sortAtomsCustom = pConv->IsOption("z", OBConversion::OUTOPTIONS);
std::vector<OBAtom *> atoms_sorted;
atoms_sorted.reserve(mol.NumAtoms());
FOR_ATOMS_OF_MOL(atom, mol) {
atoms_sorted.push_back(&(*atom));
}
std::vector<int> custom_sort_nums;
if (sortAtomsCustom != nullptr)
{
vector<string> vs;
tokenize(vs, sortAtomsCustom);
for(size_t i = 0; i < vs.size(); ++i)
custom_sort_nums.push_back(OBElements::GetAtomicNum(vs[i].c_str()));
}
compare_sort_items csi(custom_sort_nums, sortAtomsNum != nullptr);
std::stable_sort(atoms_sorted.begin(), atoms_sorted.end(), csi);
std::vector<std::pair<int, int> > atomicNums;
int prev_anum = -20; for(int i = 0; i < atoms_sorted.size(); i++)
{
const int anum = atoms_sorted[i]->GetAtomicNum();
if( prev_anum != anum )
{
std::pair<int, int> x(anum, 1);
atomicNums.push_back(x);
}
else
{
if(atomicNums.size() > 0)
atomicNums.rbegin()->second++;
}
prev_anum = anum;
}
ofs << mol.GetTitle() << endl;
ofs << "1.000 " << endl;
if (!mol.HasData(OBGenericDataType::UnitCell)) {
for (int ii = 0; ii < 3; ii++) {
snprintf(buffer, BUFF_SIZE, "0.0 0.0 0.0");
ofs << buffer << endl;
}
}
else
{
uc = static_cast<OBUnitCell*>(mol.GetData(OBGenericDataType::UnitCell));
cell = uc->GetCellVectors();
for (vector<vector3>::const_iterator i = cell.begin();
i != cell.end(); ++i) {
snprintf(buffer, BUFF_SIZE, "%20.15f%20.15f%20.15f",
i->x(), i->y(), i->z());
ofs << buffer << endl;
}
}
const char *vasp4Format = pConv->IsOption("4", OBConversion::OUTOPTIONS);
if (!vasp4Format) {
for (vector< std::pair<int, int> >::const_iterator
it = atomicNums.begin(),
it_end = atomicNums.end(); it != it_end; ++it) {
snprintf(buffer, BUFF_SIZE, "%-3s ", OBElements::GetSymbol(it->first));
ofs << buffer ;
}
ofs << endl;
}
for (vector< std::pair<int, int> >::const_iterator
it = atomicNums.begin(),
it_end = atomicNums.end(); it != it_end; ++it) {
snprintf(buffer, BUFF_SIZE, "%-3u ", it->second);
ofs << buffer ;
}
ofs << endl;
bool selective = false;
FOR_ATOMS_OF_MOL(atom, mol) {
if (atom->HasData("move")){
selective = true;
break;
}
}
if (selective) {
ofs << "SelectiveDyn" << endl;
}
ofs << "Cartesian" << endl;
for (std::vector<OBAtom *>::const_iterator it = atoms_sorted.begin();
it != atoms_sorted.end(); ++it)
{
snprintf(buffer,BUFF_SIZE, "%26.19f %26.19f %26.19f",
(*it)->GetX(), (*it)->GetY(), (*it)->GetZ());
ofs << buffer;
if (selective) {
if ((*it)->HasData("move")) {
OBPairData *cp = (OBPairData*)(*it)->GetData("move");
ofs << " " << cp->GetValue().c_str();
}
else {
ofs << " T T T";
}
}
ofs << endl;
}
return true;
}
}