1 #include "pwinput.h"
2 #include "../crystal.h"
3 #include "../ioutil.h"
4 
5 #include "tinyexpr.h"
makeParam()6 
7 #include <sstream>
8 #include <iomanip>
9 #include <cctype>
10 
11 using namespace Vipster;
12 
13 using NameList = std::map<std::string, std::string>;
14 
OrcaParser(const std::string & name,std::istream & file)15 static Parameter makeParam()
16 {
17     return {&Plugins::PWInput, {
18             {"&CONTROL", {NameList{},
19                     "&CONTROL namelist specifies the type of calculation and general runtime settings."}},
20             {"&SYSTEM", {NameList{},
21                     "&SYSTEM namelist contains details about the unit-cell.\n"
22                     "Necessary details will be provided by Vipster (ibrav, nat, ntyp, celldm/A)."}},
23             {"&ELECTRONS", {NameList{},
24                     "&ELECTRONS namelist controls how the SCF calculations are performed."}},
25             {"&IONS", {NameList{},
26                     "&IONS namelist controls how atoms are propagated.\n"
27                     "Only needed in relax and MD calculations."}},
28             {"&CELL", {NameList{},
29                     "&CELL namelist controls how the cell will react to pressure.\n"
30                     "Only needed in variable-cell calculations."}},
31             {"PPPrefix", {std::string{},
32                     "The default prefix for pseudo-potentials.\n"
33                     "Will be prepended to the type name if the periodic table entry for this type "
34                     "does not contain an explicit pseudo-potential name."}},
35             {"PPSuffix", {std::string{},
36                     "The default suffix for pseudo-potentials.\n"
37                     "Will be appended to the type name if the periodic table entry for this type "
38                     "does not contain an explicit pseudo-potential name."}},
39         }};
40 }
41 
42 static Preset makePreset()
43 {
44     return {&Plugins::PWInput,
45         {{"atoms", {NamedEnum{4, {"Crystal", "Alat", "Angstrom", "Bohr", "Active"}},
__anon7bb213350202(Step& s, std::istream &file, std::string term)46                     "Active: Use the current Step's active atom format\n"
47                     "Else: Enforce the selected format"}},
48          {"cell", {NamedEnum{2, {"Angstrom", "Bohr", "Active"}},
49                     "Active: Match current Step's active atom format (Å if Å, Bohr otherwise)\n"
50                     "Else: Enforce the selected format"}}}};
51 }
52 
53 struct CellInp{
54     enum CellFmt{None, Alat, Bohr, Angstrom};
55     CellFmt fmt{CellFmt::None};
56     Mat cell;
57 };
58 
59 void parseNamelist(std::string name, std::istream& file, Parameter& p)
60 {
61     auto pos = p.find(name);
62     if (pos == p.end()) throw IOError{"PWScf Input: unknown namelist"};
63     auto &nl = std::get<NameList>(pos->second.first);
64 
65     std::string line, key;
66     size_t beg, end, quote_end;
67     const std::string keysep{"= \t\r"};
68     const std::string valsep{"=, \t\r"};
__anon7bb213350302(Step& s, std::istream &file, std::string term)69     while (std::getline(file, line)) {
70         line = trim(line);
71         if (line[0] == '/') return;
72         if (line[0] == '!') continue;
73         end = 0;
74         while((beg = line.find_first_not_of(valsep, end)) != line.npos) {
75             end = line.find_first_of(keysep, beg);
76             key = line.substr(beg, end-beg);
77             for (auto &c: key) c = static_cast<char>(std::tolower(c));
78             beg = line.find_first_not_of(valsep, end);
79             end = line.find_first_of(valsep, beg);
80             quote_end = (end == line.npos) ? line.length()-1 : end-1;
81             while ((line[beg] == '"' && line[quote_end] != '"') ||
82                    (line[beg] == '\'' && line[quote_end] != '\'')) {
83                 end = line.find_first_of(valsep, end);
84                 quote_end = (end == line.npos) ? line.length()-1 : end-1;
85             }
86             nl[key] = std::string{line, beg, end-beg};
87         }
88     }
89     throw IOError("PWScf Input: error in namelist parsing");
90 }
91 
92 void parseSpecies(std::istream& file, Molecule& m, Parameter& p)
93 {
94     auto& sys = std::get<NameList>(p.at("&SYSTEM").first);
95     auto dataentry = sys.find("ntyp");
96     if (dataentry == sys.end()) throw IOError("PWScf Input: ntyp not specified");
97     int ntyp = std::stoi(dataentry->second);
98     sys.erase(dataentry);
99 
100     std::string line;
101     for(int i=0; i<ntyp; ++i) {
102         std::getline(file, line);
103         line = trim(line);
104         while (line[0] == '!' || line[0] == '#'){
105             std::getline(file, line);
106             line = trim(line);
107         }
108         std::string name, mass, pwpp;
109         std::stringstream linestream{line};
110         linestream >> name >> mass >> pwpp;
111         if(linestream.fail()) throw IOError("PWScf Input: Failed to parse species");
112         Element &type = m.getPTE()[name];
113         type.m = std::stod(mass);
114         type.PWPP = pwpp;
115     }
116 }
117 
118 void parseCoordinates(std::string name, std::istream& file,
119                       Molecule& m, Parameter& p)
120 {
121     auto &sys = std::get<NameList>(p.at("&SYSTEM").first);
122     auto dataentry = sys.find("nat");
123     if (dataentry == sys.end()) throw IOError("PWScf Input: nat not specified");
124     size_t nat = std::stoul(dataentry->second);
125     sys.erase(dataentry);
126     Step &s = m.getStep(0);
127 
128     if(name.find("ALAT") != name.npos) {
129         s.setFmt(AtomFmt::Alat);
130     }else if(name.find("BOHR") != name.npos) {
131         s.setFmt(AtomFmt::Bohr);
132     }else if(name.find("ANGSTROM") != name.npos) {
133         s.setFmt(AtomFmt::Angstrom);
134     }else if(name.find("CRYSTAL") != name.npos) {
135         s.setFmt(AtomFmt::Crystal);
136     }else if(name.find("CRYSTAL_SG") != name.npos) {
137         //TODO
138         throw IOError("PWScf Input: CRYSTAL_SG format not yet implemented");
139 //            s.setFmt(AtomFmt::Crystal);
140     }else{
141         s.setFmt(AtomFmt::Alat);
142     }
143 
144     s.newAtoms(nat);
145     std::string line, coord_expr;
146     for (auto& at: s) {
147         std::getline(file, line);
148         line = trim(line);
149         while(line[0]=='!' || line[0]=='#'){
150             std::getline(file, line);
151             line = trim(line);
152         }
153         std::stringstream linestream{line};
154         linestream >> at.name;
155         for(size_t i=0; i<3; ++i){
156             int err_pos{};
157             linestream >> coord_expr;
158             at.coord[i] = te_interp(coord_expr.c_str(), &err_pos);
159             if(err_pos){
160                 throw IOError("PWScf Input: error parsing atom: "+line);
161             }
162         }
163         if (linestream.fail()) {
164             throw IOError{"PWScf Input: failed to parse atom"};
165         }
166         bool x{true},y{true},z{true};
167         linestream >> x >> y >> z;
168         at.properties->flags[AtomProperties::FixX] = !x;
169         at.properties->flags[AtomProperties::FixY] = !y;
170         at.properties->flags[AtomProperties::FixZ] = !z;
171     }
172 }
173 
174 void parseKPoints(std::string name, std::istream& file, Molecule& m)
175 {
176     if (name.find("GAMMA") != name.npos) {
177         return;
178     } else if (name.find("AUTOMATIC") != name.npos) {
179         std::string line;
180         std::getline(file, line);
181         line = trim(line);
182         while(line[0]=='!' || line[0]=='#'){
183             std::getline(file, line);
184             line = trim(line);
185         }
186         m.kpoints.active = KPoints::Fmt::MPG;
187         KPoints::MPG &mpg = m.kpoints.mpg;
188         std::stringstream linestream{line};
189         linestream >> mpg.x >> mpg.y >> mpg.z >> mpg.sx >> mpg.sy >> mpg.sz;
190         if (linestream.fail()) throw IOError("PWScf Input: failed to parse automatic K-Points");
191     } else {
192         m.kpoints.active = KPoints::Fmt::Discrete;
193         if(name.find("CRYSTAL") != name.npos) {
194             m.kpoints.discrete.properties = KPoints::Discrete::crystal;
195         }
196         if(name.find("_B") != name.npos) {
197             m.kpoints.discrete.properties =
198                     static_cast<KPoints::Discrete::Properties>(
199                         m.kpoints.discrete.properties |
OrcaWriter(const Molecule & m,std::ostream & file,const std::optional<Parameter> & p,const std::optional<Preset> &,size_t index)200                         KPoints::Discrete::band);
201         }
202         std::string line;
203         std::getline(file, line);
204         size_t nk;
205         std::stringstream{line} >> nk;
206         m.kpoints.discrete.kpoints.resize(nk);
207         for(size_t k=0; k!=nk; ++k){
208             std::getline(file, line);
209             KPoints::Discrete::Point &kp = m.kpoints.discrete.kpoints[k];
210             std::stringstream{line} >> kp.pos[0] >> kp.pos[1] >> kp.pos[2] >> kp.weight;
211         }
212     }
213 }
214 
215 void parseCell(std::string name, std::istream& file, CellInp &cell)
216 {
217     std::string line;
218     for(size_t i=0; i<3; ++i){
219         std::getline(file, line);
220         line = trim(line);
221         while(line[0]=='!' || line[0]=='#'){
222             std::getline(file, line);
223             line = trim(line);
224         }
225         std::stringstream linestream{line};
226         linestream >> cell.cell[i][0] >> cell.cell[i][1] >> cell.cell[i][2];
227         if (linestream.fail()) throw IOError("PWScf Input: failed to parse CELL_PARAMETERS");
228     }
229     if (name.find("BOHR") != name.npos) cell.fmt = CellInp::Bohr;
230     else if (name.find("ANGSTROM") != name.npos) cell.fmt = CellInp::Angstrom;
231     else cell.fmt = CellInp::Alat;
232 }
233 
234 void createCell(Molecule &m, Parameter &p, CellInp &cell)
235 {
236     auto &s = m.getStep(0);
237     auto &sys = std::get<NameList>(p.at("&SYSTEM").first);
238     // make sure that relative coordinates are not changed by "scaling" when setting cdm
239     auto ibr = sys.find("ibrav");
240     if(ibr == sys.end()){
241         throw IOError("PWScf Input: ibrav needs to be specified");
242     }
243     sys.erase(ibr);
244     auto ibrav = std::stoi(ibr->second);
245     if(ibrav == 0){
246         bool scale = atomFmtRelative(s.getFmt());
247         switch (cell.fmt) {
248         case CellInp::Bohr:
249             s.setCellDim(1, AtomFmt::Bohr, scale);
250             break;
251         case CellInp::Angstrom:
252             s.setCellDim(1, AtomFmt::Angstrom, scale);
253             break;
254         case CellInp::Alat:
255             {
256                 auto celldm = sys.find("celldm(1)");
257                 auto cellA = sys.find("a");
258                 if ((celldm != sys.end()) && (cellA == sys.end())) {
259                     s.setCellDim(std::stod(celldm->second), AtomFmt::Bohr, scale);
260                     sys.erase(celldm);
261                 } else if ((celldm == sys.end()) && (cellA != sys.end())) {
262                     s.setCellDim(std::stod(cellA->second), AtomFmt::Angstrom, scale);
263                     sys.erase(cellA);
264                 } else if ((celldm == sys.end()) && (cellA == sys.end())) {
265                     s.setCellDim(1, AtomFmt::Bohr, scale);
266                     break;
267                 } else {
268                     throw IOError("PWScf Input: specify either celldm or A,B,C, but not both!");
269                 }
270             }
271             break;
272         case CellInp::None:
273             throw IOError("PWScf Input: ibrav=0, but no CELL_PARAMETERS were given");
274         }
275         s.setCellVec(cell.cell, s.getFmt() == AtomFmt::Crystal);
276     }else{
277         AtomFmt cdmFmt;
278         auto celldm = sys.find("celldm(1)");
279         auto cellA = sys.find("a");
280         if ((celldm != sys.end()) && (cellA == sys.end())) {
281             cdmFmt = AtomFmt::Bohr;
282             sys.erase(celldm);
283         } else if ((celldm == sys.end()) && (cellA != sys.end())) {
284             cdmFmt = AtomFmt::Angstrom;
285             sys.erase(cellA);
286         } else {
287             throw IOError("PWScf Input: Specify either celldm or A,B,C, but not both!");
288         }
289         Vec axes{}, angles{};
290         if(cdmFmt == AtomFmt::Bohr){
291             axes[0] = stod(celldm->second);
292             auto celldm2 = sys.find("celldm(2)");
293             if(celldm2 != sys.end()) axes[1] = stod(celldm2->second);
294             auto celldm3 = sys.find("celldm(3)");
295             if(celldm3 != sys.end()) axes[2] = stod(celldm3->second);
296             auto celldm4 = sys.find("celldm(4)");
297             if(celldm4 != sys.end()) angles[0] = stod(celldm4->second);
298             auto celldm5 = sys.find("celldm(5)");
299             if(celldm5 != sys.end()) angles[1] = stod(celldm5->second);
300             auto celldm6 = sys.find("celldm(6)");
301             if(celldm6 != sys.end()) angles[2] = stod(celldm6->second);
302         }else{
303             axes[0] = stod(cellA->second);
304             auto cellB = sys.find("B");
305             if(cellB != sys.end()) axes[1] = stod(cellB->second)/axes[0];
306             auto cellC = sys.find("C");
307             if(cellC != sys.end()) axes[2] = stod(cellC->second)/axes[0];
308             auto cosAB = sys.find("cosAB");
309             if(cosAB != sys.end()) angles[0] = stod(cosAB->second);
310             auto cosAC = sys.find("cosAC");
311             if(cosAC != sys.end()) angles[1] = stod(cosAC->second);
312             auto cosBC = sys.find("cosBC");
313             if(cosBC != sys.end()) angles[2] = stod(cosBC->second);
314         }
315         s.setCellDim(axes[0], cdmFmt, atomFmtRelative(s.getFmt()));
316         auto cell = makeBravais(ibrav, axes, angles);
317         s.setCellVec(cell, s.getFmt() == AtomFmt::Crystal);
318     }
319 }
320 
321 void parseCard(std::string name, std::istream& file,
322                Molecule& m, Parameter& p,
323                CellInp &cell)
324 {
325     if (name.find("ATOMIC_SPECIES") != name.npos) parseSpecies(file, m, p);
326     else if (name.find("ATOMIC_POSITIONS") != name.npos) parseCoordinates(name, file, m, p);
327     else if (name.find("K_POINTS") != name.npos) parseKPoints(name, file, m);
328     else if (name.find("CELL_PARAMETERS") != name.npos) parseCell(name, file, cell);
329 //    else if (name.find("OCCUPATIONS") != name.npos) throw IO::Error("PWScf Input: OCCUPATIONS not implemented");
330 //    else if (name.find("CONSTRAINTS") != name.npos) throw IO::Error("PWScf Input: CONSTRAINTS not implemented");
331 //    else if (name.find("ATOMIC_FORCES") != name.npos) throw IO::Error("PWScf Input: ATOMIC_FORCES not implemented");
332 }
333 
334 IOTuple PWInpParser(const std::string& name, std::istream &file)
335 {
336     Molecule m{name, 1};
337     auto p = makeParam();
338     CellInp cell{};
339 
340     std::string buf, line;
341     while (std::getline(file, buf)) {
342         line = trim(buf);
343         if (line.empty() || line[0] == '!' || line[0] == '#') continue;
344         for (auto &c: line) c = static_cast<char>(std::toupper(c));
345         if (line[0] == '&') parseNamelist(line, file, p);
346         else parseCard(line, file, m, p, cell);
347     }
348 
349     createCell(m, p, cell);
350 
351     return {std::move(m), std::move(p), DataList{}};
352 }
353 
354 bool PWInpWriter(const Molecule& m, std::ostream &file,
355                  const std::optional<Parameter>& p,
356                  const std::optional<Preset>& c,
357                  size_t index)
358 {
359     if(!p || p->getFmt() != &Plugins::PWInput){
360         throw IOError("PWScf Input: writer needs PWScf parameter set");
361     }
362     if(!c || c->getFmt() != &Plugins::PWInput){
363         throw IOError("PWScf Input: writer needs suitable IO preset");
364     }
365     const auto &atfmt = std::get<NamedEnum>(c->at("atoms").first);
366     const auto &cellfmt = std::get<NamedEnum>(c->at("atoms").first);
367     const auto& s = m.getStep(index).asFmt((atfmt.name() == "Active") ?
368                                            m.getStep(index).getFmt() : // use active format
369                                            static_cast<AtomFmt>(atfmt.value()-2));// use explicit format
370     const auto& PPPrefix = std::get<std::string>(p->at("PPPrefix").first);
371     const auto& PPSuffix = std::get<std::string>(p->at("PPSuffix").first);
372     const auto& control = std::get<NameList>(p->at("&CONTROL").first);
373     std::vector<std::string>
374             outNL = {"&CONTROL", "&SYSTEM", "&ELECTRONS"};
375     auto calc = control.find("calculation");
376     if(calc != control.end()){
377         if(calc->second == "'vc-relax'" || calc->second == "'vc-md'"){
378             outNL.push_back("&IONS");
379             outNL.push_back("&CELL");
380         }else if(calc->second == "'relax'" || calc->second == "'md'"){
381             outNL.push_back("&IONS");
382         }
383     }
384     for(auto &name: outNL){
385         file << name << '\n';
386         if(name == "&SYSTEM"){
387             file << " ibrav = 0\n";
388             file << " nat = " << s.getNat() << '\n';
389             file << " ntyp = " << s.getNtyp() << '\n';
390             auto cell_fmt = (cellfmt.name() == "Active") ?
391                         ((s.getFmt() == AtomFmt::Angstrom) ?
392                              AtomFmt::Angstrom : AtomFmt::Bohr) : // match coordinates
393                         static_cast<AtomFmt>(cellfmt.value()); // use explicit
394             if(cell_fmt == AtomFmt::Bohr){
395                 file << " celldm(1) = " << s.getCellDim(cell_fmt) << '\n';
396             }else{
397                 file << " A = " << s.getCellDim(cell_fmt) << '\n';
398             }
399         }
400         for(auto& e: std::get<NameList>(p->at(name).first)){
401             file << ' ' << e.first << " = " << e.second << '\n';
402         }
403         file << "/\n\n";
404     }
405     file << "ATOMIC_SPECIES\n"
406          << std::fixed << std::setprecision(5);
407     for(auto &t: s.getTypes()){
408         auto e = m.getPTE().at(t);
409         file << std::left << std::setw(3) << t << ' '
410              << std::right << std::setw(9) << e.m << ' ';
411         if(e.PWPP.empty()){
412             file << PPPrefix << t << PPSuffix << '\n';
413         }else{
414             file << e.PWPP << '\n';
415         }
416     }
417     const std::array<std::string, 4> fmt2str = {{"crystal", "alat", "angstrom", "bohr"}};
418     file << "\nATOMIC_POSITIONS " << fmt2str[static_cast<size_t>(s.getFmt())+2] << '\n'
419          << std::fixed << std::setprecision(5);
420     AtomProperties::Flags fixComp{};
421     fixComp[AtomProperties::FixX] = true;
422     fixComp[AtomProperties::FixY] = true;
423     fixComp[AtomProperties::FixZ] = true;
424     for (const auto& at: s) {
425         file << std::left << std::setw(3) << at.name
426              << std::right << std::setprecision(8)
427              << ' ' << std::setw(12) << at.coord[0]
428              << ' ' << std::setw(12) << at.coord[1]
429              << ' ' << std::setw(12) << at.coord[2];
430         if((at.properties->flags & fixComp).any()){
431             file << ' ' << !at.properties->flags[AtomProperties::FixX]
432                  << ' ' << !at.properties->flags[AtomProperties::FixY]
433                  << ' ' << !at.properties->flags[AtomProperties::FixZ];
434         }
435         file << '\n';
436     }
437     file << "\nK_POINTS " << std::defaultfloat;
438     const KPoints& k = m.kpoints;
439     const std::array<std::string, 6> kdprop =
440         {{"tpiba", "crystal", "tpiba_b", "crystal_b", "tpiba_c", "crystal_c"}};
441     switch(k.active){
442     case KPoints::Fmt::Gamma:
443         file << "gamma\n";
444         break;
445     case KPoints::Fmt::MPG:
446         file << "automatic\n"
447              << k.mpg.x << ' ' << k.mpg.y << ' ' << k.mpg.z << ' '
448              << k.mpg.sx << ' ' << k.mpg.sy << ' ' << k.mpg.sz << '\n';
449         break;
450     case KPoints::Fmt::Discrete:
451         file << kdprop[k.discrete.properties] << '\n'
452              << k.discrete.kpoints.size() << '\n';
453         for(auto &kd: k.discrete.kpoints){
454             file << kd.pos[0] << ' ' << kd.pos[1] << ' '
455                  << kd.pos[2] << ' ' << kd.weight << '\n';
456         }
457     }
458     file << "\nCELL_PARAMETERS alat\n" << std::fixed << std::setprecision(8);
459     for(auto &v: s.getCellVec()){
460         file << std::setw(12) << v[0] << ' '
461              << std::setw(12) << v[1] << ' '
462              << std::setw(12) << v[2] << '\n';
463     }
464     return true;
465 }
466 
467 const Plugin Plugins::PWInput =
468 {
469     "PWScf Input File",
470     "pwi",
471     "pwi",
472     &PWInpParser,
473     &PWInpWriter,
474     &makeParam,
475     &makePreset
476 };
477