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