335 std::vector<RawStartingNsEigenvalue> raw_starting_ns_eigenvalues;
336 std::ifstream input_file(filename);
337 if (!input_file.is_open())
338 throw std::runtime_error(
"Cannot open input file: " + filename);
350 std::string cell_parameters_units;
351 int cell_parameters_row = 0;
352 bool has_cell_parameters =
false;
358 Real lattice_constant_a = 0.0, lattice_constant_b = 0.0, lattice_constant_c = 0.0;
359 Real cosine_ab = 0.0, cosine_ac = 0.0, cosine_bc = 0.0;
360 bool has_crystallographic_constants =
false;
367 std::string atomic_positions_units =
"crystal";
376 bool has_dftd3_version =
false;
377 bool has_dftd3_threebody =
false;
379 while (std::getline(input_file, line)) {
385 line = strip_comment(line);
386 std::string trimmed = trim(line);
390 std::string lower = to_lower(trimmed);
404 if (lower[0] ==
'&') {
406 lower.find_first_not_of(
"abcdefghijklmnopqrstuvwxyz0123456789_", 1);
407 if (name_end == std::string::npos)
408 name_end = lower.size();
409 section = lower.substr(1, name_end - 1);
411 if (section ==
"end") {
416 throw std::runtime_error(
417 "QE namelist '&" + section +
418 "' is not supported. Q3 reads &control, &system, &electrons, &ions and "
419 "&cell, and refuses the rest rather than ignoring the physics in them.");
420 line = trimmed.substr(name_end);
421 trimmed = trim(line);
424 lower = to_lower(trimmed);
434 bool line_closes_namelist =
false;
436 line_closes_namelist = strip_namelist_terminator(line, section);
437 trimmed = trim(line);
438 if (trimmed.empty()) {
442 lower = to_lower(trimmed);
444 if (lower[0] ==
'/') {
450 if (lower.find(
"atomic_species") != std::string::npos) {
451 section =
"atomic_species";
454 if (lower.find(
"atomic_positions") != std::string::npos) {
455 section =
"atomic_positions";
456 atomic_positions_units = read_atomic_positions_units(lower);
459 if (lower.find(
"k_points") != std::string::npos) {
460 section =
"k_points";
477 bool looks_like_card = lower.find(
'=') == std::string::npos;
479 if (looks_like_card && lower.rfind(
"hubbard", 0) == 0) {
486 if (lower.find(
"ortho-atomic") != std::string::npos)
488 else if (lower.find(
"atomic") != std::string::npos)
491 throw std::runtime_error(
492 "QE HUBBARD card projector type in '" + trimmed +
493 "' is not supported; Q3 implements (atomic) and (ortho-atomic)");
496 for (
const char* unsupported_card : {
"occupations",
500 "additional_k_points",
504 if (looks_like_card && lower.rfind(unsupported_card, 0) == 0)
505 throw std::runtime_error(
506 std::string(
"QE card '") + trimmed +
507 "' is not supported. Q3 refuses cards it cannot honour rather than "
508 "ignoring them, because an ignored card produces a converged result "
509 "for a different calculation than the one requested.");
512 if (lower.find(
"cell_parameters") != std::string::npos) {
513 section =
"cell_parameters";
514 cell_parameters_row = 0;
515 cell_parameters_units = read_cell_parameters_units(lower);
527 classify_namelist_keys(trimmed, section);
529 if (section ==
"control") {
530 if (extract_key_value(line,
"pseudo_dir", value))
532 if (extract_key_value(line,
"calculation", value)) {
539 std::string requested = strip_quotes(value);
540 if (requested ==
"scf")
542 else if (requested ==
"nscf" || requested ==
"bands")
544 else if (requested ==
"relax")
546 else if (requested ==
"vc-relax")
551 if (extract_key_value(line,
"prefix", value))
553 if (extract_key_value(line,
"outdir", value))
562 if (extract_key_value(line,
"verbosity", value)) {
563 std::string requested = to_lower(strip_quotes(value));
564 if (requested ==
"high" || requested ==
"debug")
566 else if (requested ==
"low" || requested ==
"medium" ||
567 requested ==
"minimal" || requested ==
"default")
570 throw std::runtime_error(
571 "verbosity='" + requested +
572 "' is not a Quantum ESPRESSO verbosity; use 'high', 'debug', "
573 "'default', 'low', 'medium' or 'minimal'");
581 if (extract_key_value(line,
"npool", value))
588 if (extract_key_value(line,
"restart_mode", value)) {
589 std::string requested = to_lower(strip_quotes(value));
590 if (requested !=
"from_scratch")
591 throw std::runtime_error(
592 "restart_mode='" + requested +
593 "' is not supported; Q3 cannot resume a QE run's saved state");
601 if (extract_key_value(line,
"nstep", value))
603 if (extract_key_value(line,
"forc_conv_thr", value))
605 if (extract_key_value(line,
"etot_conv_thr", value))
607 }
else if (section ==
"system") {
608 if (extract_key_value(line,
"ibrav", value))
616 for (
int index = 1; index <= 6; index++) {
617 if (!extract_key_value(line,
"celldm(" + std::to_string(index) +
")", value))
619 Real dimension = fortran_to_double(value);
647 for (
const auto& [key, target] :
648 {std::pair<const char*, Real*>{
"A", &lattice_constant_a},
649 {
"B", &lattice_constant_b},
650 {
"C", &lattice_constant_c},
651 {
"cosAB", &cosine_ab},
652 {
"cosAC", &cosine_ac},
653 {
"cosBC", &cosine_bc}}) {
654 if (extract_key_value(line, key, value)) {
655 *target = fortran_to_double(value);
656 has_crystallographic_constants =
true;
659 if (extract_key_value(line,
"nat", value))
661 if (extract_key_value(line,
"ntyp", value))
663 if (extract_key_value(line,
"ecutwfc", value))
665 if (extract_key_value(line,
"ecutrho", value))
667 if (extract_key_value(line,
"nbnd", value))
669 if (extract_key_value(line,
"input_dft", value))
671 if (extract_key_value(line,
"nspin", value)) {
674 throw std::runtime_error(
675 "nspin must be 1 (unpolarized) or 2 (collinear LSDA), got " + value +
676 " (noncollinear nspin=4 is not supported)");
684 if (extract_key_value(line,
"number_of_spin_components", value))
685 throw std::runtime_error(
686 "'number_of_spin_components' is not a QE namelist key; use nspin instead");
687 if (extract_key_value(line,
"starting_magnetization", value)) {
692 int species_index = 1;
693 size_t key_position = to_lower(line).find(
"starting_magnetization");
694 size_t parenthesis_position = line.find(
'(', key_position);
695 size_t equals_position = line.find(
'=', key_position);
696 if (parenthesis_position != std::string::npos &&
697 equals_position != std::string::npos &&
698 parenthesis_position < equals_position) {
700 species_index = std::stoi(line.substr(parenthesis_position + 1));
701 }
catch (
const std::exception&) {
702 throw std::runtime_error(
703 "Cannot parse starting_magnetization species index in line: " +
706 if (species_index < 1)
707 throw std::runtime_error(
708 "starting_magnetization index must be >= 1, got " +
709 std::to_string(species_index));
732 std::string lower_line = to_lower(line);
733 const std::string seeding_key =
"starting_ns_eigenvalue";
734 for (
size_t key_position = lower_line.find(seeding_key);
735 key_position != std::string::npos;
736 key_position = lower_line.find(seeding_key, key_position + 1)) {
737 if (!is_whole_key_at(lower_line, key_position, seeding_key.size()))
739 size_t parenthesis_position = line.find(
'(', key_position);
740 size_t closing_position = line.find(
')', key_position);
741 size_t equals_position = line.find(
'=', key_position);
742 if (parenthesis_position == std::string::npos ||
743 closing_position == std::string::npos ||
744 equals_position == std::string::npos ||
745 equals_position < parenthesis_position)
746 throw std::runtime_error(
747 "starting_ns_eigenvalue needs explicit (m,ispin,ityp) indices, got: " +
749 std::string index_list = line.substr(
750 parenthesis_position + 1, closing_position - parenthesis_position - 1);
751 std::vector<int> indices;
752 std::stringstream index_stream(index_list);
753 std::string index_token;
754 while (std::getline(index_stream, index_token,
','))
756 indices.push_back(std::stoi(trim(index_token)));
757 }
catch (
const std::exception&) {
758 throw std::runtime_error(
759 "Cannot parse starting_ns_eigenvalue indices in line: " +
762 if (indices.size() != 3)
763 throw std::runtime_error(
764 "starting_ns_eigenvalue needs exactly (m,ispin,ityp), got " +
765 std::to_string(indices.size()) +
" indices in: " + trim(line));
768 size_t value_start = equals_position + 1;
769 while (value_start < line.size() &&
770 (line[value_start] ==
' ' || line[value_start] ==
'\t'))
772 size_t value_end = line.find_first_of(
",!\n\r", value_start);
773 if (value_end == std::string::npos)
774 value_end = line.size();
776 fortran_to_double(trim(line.substr(value_start, value_end - value_start)));
778 if (indices[0] < 1 || indices[1] < 1 || indices[1] > 2 || indices[2] < 1)
779 throw std::runtime_error(
780 "starting_ns_eigenvalue indices must be m >= 1, ispin in {1,2}, "
783 if (eigenvalue < 0.0 || eigenvalue > 1.0)
784 throw std::runtime_error(
785 "starting_ns_eigenvalue value must be in [0, 1], got " +
786 std::to_string(eigenvalue));
787 raw_starting_ns_eigenvalues.push_back(
788 {indices[0], indices[1], indices[2], eigenvalue});
790 if (extract_key_value(line,
"degauss", value))
792 if (extract_key_value(line,
"tot_charge", value))
794 if (extract_key_value(line,
"smearing", value))
795 data.
smearing = strip_quotes(value);
796 if (extract_key_value(line,
"occupations", value)) {
797 std::string occupations = strip_quotes(value);
798 if (to_lower(occupations) ==
"smearing" && data.
smearing ==
"none") {
802 if (extract_key_value(line,
"vdw_corr", value)) {
803 std::string vdw_corr = to_lower(strip_quotes(value));
804 if (vdw_corr ==
"dft-d3" || vdw_corr ==
"grimme-d3") {
814 }
else if (vdw_corr ==
"none" || vdw_corr.empty()) {
822 throw std::runtime_error(
823 "vdw_corr = '" + vdw_corr +
824 "' is not supported; the only dispersion correction Q3 implements "
825 "is DFT-D3 with Becke-Johnson damping ('dft-d3' or 'grimme-d3')");
833 if (extract_key_value(line,
"dftd3_version", value)) {
834 if (std::stoi(value) != 4)
835 throw std::runtime_error(
836 "dftd3_version = " + value +
837 " is not supported; Q3 implements version 4 (Becke-Johnson damping)");
838 has_dftd3_version =
true;
840 if (extract_key_value(line,
"dftd3_threebody", value)) {
841 if (fortran_logical_is_true(value))
842 throw std::runtime_error(
843 "dftd3_threebody = .true. is not supported; Q3 implements the "
844 "two-body D3(BJ) term only");
845 has_dftd3_threebody =
true;
847 if (extract_key_value(line,
"nosym", value))
849 if (extract_key_value(line,
"assume_isolated", value)) {
854 std::string requested = to_lower(strip_quotes(value));
855 if (requested ==
"makov-payne" || requested ==
"m-p" || requested ==
"mp")
857 else if (requested ==
"martyna-tuckerman" || requested ==
"m-t" ||
860 else if (requested ==
"none")
863 throw std::runtime_error(
864 "assume_isolated='" + requested +
865 "' is not supported; Q3 implements 'makov-payne' and "
866 "'martyna-tuckerman'");
868 }
else if (section ==
"ions") {
869 if (extract_key_value(line,
"ion_dynamics", value)) {
875 std::string requested = to_lower(strip_quotes(value));
876 if (requested !=
"bfgs")
877 throw std::runtime_error(
878 "ion_dynamics='" + requested +
879 "' is not supported; Q3's relaxation is BFGS, and the damped-dynamics "
880 "and molecular-dynamics paths are not implemented");
882 }
else if (section ==
"cell") {
883 if (extract_key_value(line,
"cell_dynamics", value)) {
884 std::string requested = to_lower(strip_quotes(value));
885 if (requested !=
"bfgs")
886 throw std::runtime_error(
887 "cell_dynamics='" + requested +
888 "' is not supported; Q3's variable-cell relaxation is BFGS on an ASE "
891 }
else if (section ==
"electrons") {
892 if (extract_key_value(line,
"conv_thr", value))
894 if (extract_key_value(line,
"startingpot", value)) {
896 static const std::map<std::string, bool> starts_from_saved_density = {
897 {
"atomic",
false}, {
"file",
true}};
898 const std::string start = to_lower(strip_quotes(value));
899 const auto found = starts_from_saved_density.find(start);
900 if (found == starts_from_saved_density.end())
901 throw std::runtime_error(
"startingpot='" + start +
902 "' is not supported; use 'atomic' or 'file'");
905 if (extract_key_value(line,
"mixing_beta", value))
907 if (extract_key_value(line,
"mixing_mode", value)) {
923 std::string requested = to_lower(strip_quotes(value));
924 if (requested ==
"plain") {
926 }
else if (requested ==
"tf") {
927 log::info(
" QE input requests mixing_mode='{}'; using Broyden with "
928 "Kerker preconditioning, which is that same average "
929 "Thomas-Fermi screening",
930 strip_quotes(value));
933 }
else if (requested ==
"local-tf") {
940 if (extract_key_value(line,
"mixing_ndim", value))
942 if (extract_key_value(line,
"electron_maxstep", value))
949 if (extract_key_value(line,
"scf_must_converge", value) &&
950 !fortran_logical_is_true(value))
951 throw std::runtime_error(
952 "scf_must_converge = .false. is not supported; Q3 always requires the "
953 "SCF to converge before reporting a result");
954 if (extract_key_value(line,
"mixing_fixed_ns", value)) {
961 Real fixed_iterations = fortran_to_double(value);
962 if (fixed_iterations < 0.0 || fixed_iterations != std::floor(fixed_iterations))
963 throw std::runtime_error(
964 "mixing_fixed_ns must be a non-negative whole number, got '" + value +
968 if (extract_key_value(line,
"diagonalization", value)) {
977 std::string requested = strip_quotes(value);
978 if (requested ==
"cg" || requested ==
"ppcg" || requested ==
"paro") {
979 log::info(
" QE input requests diagonalization='{}'; using Davidson "
980 "(same fixed point, different iteration path)",
987 }
else if (section ==
"atomic_species") {
988 std::istringstream stream(trimmed);
991 if (!species.
symbol.empty()) {
992 extract_real(stream, species.
mass, trimmed,
"ATOMIC_SPECIES");
994 data.
species.push_back(species);
996 }
else if (section ==
"atomic_positions") {
997 std::istringstream stream(trimmed);
999 Real x_coordinate = 0.0, y_coordinate = 0.0, z_coordinate = 0.0;
1001 if (!symbol.empty()) {
1002 extract_real(stream, x_coordinate, trimmed,
"ATOMIC_POSITIONS");
1003 extract_real(stream, y_coordinate, trimmed,
"ATOMIC_POSITIONS");
1004 extract_real(stream, z_coordinate, trimmed,
"ATOMIC_POSITIONS");
1005 const std::array<bool, 3> movable_components =
1006 read_position_flags(stream, trimmed);
1007 int species_index = -1;
1008 for (
int index = 0; index < static_cast<int>(data.
species.size()); index++) {
1009 if (data.
species[index].symbol == symbol) {
1010 species_index = index;
1014 if (species_index >= 0) {
1017 atom.
position = {x_coordinate, y_coordinate, z_coordinate};
1019 data.
atoms.push_back(atom);
1022 }
else if (section ==
"k_points") {
1023 std::istringstream stream(trimmed);
1028 }
else if (section ==
"hubbard") {
1035 std::istringstream card_stream(trimmed);
1036 std::string parameter, target;
1038 card_stream >> parameter >> target;
1039 std::string parameter_lower = to_lower(parameter);
1040 if (parameter_lower !=
"u")
1041 throw std::runtime_error(
1042 "QE HUBBARD parameter '" + parameter +
1043 "' is not supported; Q3 implements the Dudarev U only");
1044 auto separator = target.find(
'-');
1045 if (separator == std::string::npos)
1046 throw std::runtime_error(
"QE HUBBARD entry '" + trimmed +
1047 "' is not of the form U <species>-<shell> <eV>");
1054 extract_real(card_stream, value, trimmed,
"HUBBARD");
1055 data.
hubbard.push_back({target.substr(0, separator),
1056 target.substr(separator + 1),
1058 }
else if (section ==
"cell_parameters") {
1059 if (cell_parameters_row < 3) {
1060 std::istringstream stream(trimmed);
1061 for (
int column = 0; column < 3; column++) {
1062 extract_real(stream,
1063 cell_parameters_vectors[cell_parameters_row][column],
1067 cell_parameters_row++;
1068 if (cell_parameters_row == 3) {
1069 has_cell_parameters =
true;
1075 if (line_closes_namelist)
1080 refuse_dftd3_defaults_q3_does_not_implement(has_dftd3_version, has_dftd3_threebody);
1086 if (has_crystallographic_constants) {
1087 apply_crystallographic_constants(data,
1096 if (has_cell_parameters) {
1097 apply_cell_parameters(data, cell_parameters_vectors, cell_parameters_units);
1101 convert_atomic_positions_to_crystal(data, atomic_positions_units);
1110 throw std::runtime_error(
"starting_magnetization index " +
1112 " exceeds the number of species (ntyp = " +
1122 for (
const auto& raw_directive : raw_starting_ns_eigenvalues) {
1123 if (raw_directive.species_index >
static_cast<int>(data.
species.size()))
1124 throw std::runtime_error(
"starting_ns_eigenvalue species index " +
1125 std::to_string(raw_directive.species_index) +
1126 " exceeds the number of species (" +
1127 std::to_string(data.
species.size()) +
")");
1129 data.
species[
static_cast<size_t>(raw_directive.species_index - 1)].symbol,
1130 raw_directive.eigenvalue_index,
1131 raw_directive.spin_index,
1132 raw_directive.value,
1133 "starting_ns_eigenvalue"));
1141 struct RawStartingNsEigenvalue {
1142 int eigenvalue_index;
1161 static void apply_cell_parameters(InputData& data,
1162 const Matrix3x3& raw_vectors,
1163 const std::string& units_named) {
1164 if (data.bravais_lattice_index != 0)
1165 throw std::runtime_error(
"CELL_PARAMETERS requires ibrav = 0, got ibrav = " +
1166 std::to_string(data.bravais_lattice_index));
1168 std::string units = units_named;
1169 if (units.empty()) {
1170 bool has_lattice_parameter = data.cell_dimension_1 > 0.0;
1171 units = has_lattice_parameter ?
"alat" :
"bohr";
1172 log::info(
" QE input: CELL_PARAMETERS names no units; reading its rows as {} "
1173 "because {} lattice parameter is given, as pw.x does with this deprecated "
1176 has_lattice_parameter ?
"a" :
"no");
1179 if (units ==
"alat") {
1180 if (data.cell_dimension_1 <= 0.0)
1181 throw std::runtime_error(
"CELL_PARAMETERS (alat) requires celldm(1) to be set");
1182 data.lattice_vectors = raw_vectors;
1183 data.has_explicit_lattice_vectors =
true;
1188 Matrix3x3 vectors_bohr;
1189 for (
int row = 0; row < 3; ++row) {
1190 vectors_bohr[row] = raw_vectors[row] * length_to_bohr;
1199 data.set_free_lattice_vectors(vectors_bohr, data.cell_dimension_1);
1200 }
catch (
const std::runtime_error&) {
1201 throw std::runtime_error(
"CELL_PARAMETERS: lattice vector a1 has zero length");
1213 static std::string read_cell_parameters_units(
const std::string& lower_header) {
1214 const std::string card_name =
"cell_parameters";
1216 for (
char character : lower_header.substr(lower_header.find(card_name) + card_name.size()))
1217 if (std::string(
"(){}").find(character) == std::string::npos &&
1218 !std::isspace(static_cast<unsigned char>(character)))
1219 option += character;
1221 if (option.empty() || option ==
"alat" || option ==
"bohr" || option ==
"angstrom")
1223 throw std::runtime_error(
"QE CELL_PARAMETERS option '" + option +
1224 "' is not recognized; the card takes alat, bohr or angstrom");
1239 static std::string read_atomic_positions_units(
const std::string& lower_header) {
1240 const std::string card_name =
"atomic_positions";
1242 for (
char character : lower_header.substr(lower_header.find(card_name) + card_name.size()))
1243 if (std::string(
"(){}").find(character) == std::string::npos &&
1244 !std::isspace(static_cast<unsigned char>(character)))
1245 option += character;
1247 if (option.empty()) {
1248 log::info(
" QE input: ATOMIC_POSITIONS names no units; reading its rows as alat, "
1249 "as pw.x does with this deprecated spelling");
1252 if (option ==
"crystal" || option ==
"alat" || option ==
"bohr" || option ==
"angstrom")
1254 if (option ==
"crystal_sg")
1255 throw std::runtime_error(
1256 "QE ATOMIC_POSITIONS crystal_sg is not supported: it lists only the "
1257 "symmetry-inequivalent atoms and relies on space_group to generate the rest, "
1258 "which Q3 does not do. List every atom under ATOMIC_POSITIONS crystal instead.");
1259 throw std::runtime_error(
"QE ATOMIC_POSITIONS option '" + option +
1260 "' is not recognized; the card takes crystal, alat, bohr, "
1261 "angstrom or crystal_sg");
1275 static void convert_atomic_positions_to_crystal(InputData& data,
const std::string& units) {
1276 if (units ==
"crystal" || data.atoms.empty())
1279 bool has_cell = data.has_explicit_lattice_vectors ||
1280 (data.bravais_lattice_index != 0 && data.cell_dimension_1 > 0.0);
1282 throw std::runtime_error(
1283 "QE ATOMIC_POSITIONS " + units +
1284 " rows are Cartesian and need the cell to become crystal coordinates, but the "
1285 "input defines none: ibrav = 0 needs a CELL_PARAMETERS card, and any other ibrav "
1286 "needs celldm(1) or A");
1288 Matrix3x3 lattice_vectors =
1289 data.has_explicit_lattice_vectors
1290 ? data.lattice_vectors
1292 data.cell_dimension_1,
1293 data.cell_dimension_2,
1294 data.cell_dimension_3,
1295 data.cell_dimension_4,
1296 data.cell_dimension_5,
1297 data.cell_dimension_6);
1299 throw std::runtime_error(
"QE ATOMIC_POSITIONS " + units +
1300 " rows cannot be converted: the cell has zero volume");
1302 Real length_to_alat = 1.0;
1303 if (units ==
"bohr")
1304 length_to_alat = 1.0 / data.cell_dimension_1;
1305 else if (units ==
"angstrom")
1308 Matrix3x3 inverse_lattice = lattice_vectors.inverse();
1309 for (Atom& atom : data.atoms) {
1310 Vector3D cartesian = atom.position * length_to_alat;
1311 Vector3D fractional{0.0, 0.0, 0.0};
1312 for (
int column = 0; column < 3; column++)
1313 fractional[column] = cartesian[0] * inverse_lattice[0][column] +
1314 cartesian[1] * inverse_lattice[1][column] +
1315 cartesian[2] * inverse_lattice[2][column];
1316 atom.position = fractional;
1320 static std::string to_lower(std::string text) {
1321 std::transform(text.begin(), text.end(), text.begin(), ::tolower);
1325 static std::string trim(
const std::string& text) {
1326 size_t first = text.find_first_not_of(
" \t\r\n");
1327 if (first == std::string::npos)
1329 size_t last = text.find_last_not_of(
" \t\r\n");
1330 return text.substr(first, last - first + 1);
1333 static std::string strip_quotes(
const std::string& text) {
1334 std::string result = text;
1335 if (result.size() >= 2 && result.front() ==
'\'' && result.back() ==
'\'')
1336 result = result.substr(1, result.size() - 2);
1337 if (result.size() >= 2 && result.front() ==
'"' && result.back() ==
'"')
1338 result = result.substr(1, result.size() - 2);
1343 static double fortran_to_double(
const std::string& text) {
1344 std::string fixed = text;
1345 for (
auto& character : fixed) {
1346 if (character ==
'd' || character ==
'D')
1349 return std::stod(fixed);
1372 static void extract_real(std::istringstream& stream,
1374 const std::string& line,
1375 const std::string& card_name) {
1377 if (!(stream >> token))
1378 throw std::runtime_error(
"QE " + card_name +
" line '" + line +
1379 "' ends before all of its numbers were read");
1381 std::string fixed = token;
1382 for (
auto& character : fixed) {
1383 if (character ==
'd' || character ==
'D')
1386 size_t characters_consumed = 0;
1387 value = std::stod(fixed, &characters_consumed);
1388 if (characters_consumed != fixed.size())
1389 throw std::invalid_argument(
"trailing characters");
1390 }
catch (
const std::exception&) {
1391 throw std::runtime_error(
"QE " + card_name +
" line '" + line +
"': '" + token +
1392 "' is not a number");
1405 static std::array<bool, 3> read_position_flags(std::istringstream& stream,
1406 const std::string& line) {
1407 std::vector<std::string> trailing_tokens;
1409 while (stream >> token)
1410 trailing_tokens.push_back(token);
1411 if (trailing_tokens.empty())
1412 return {
true,
true,
true};
1414 std::string trailing_text;
1415 for (
const std::string& trailing_token : trailing_tokens)
1416 trailing_text += (trailing_text.empty() ?
"" :
" ") + trailing_token;
1418 trailing_tokens.size() == 3 &&
1419 std::all_of(trailing_tokens.begin(),
1420 trailing_tokens.end(),
1421 [](
const std::string& flag) { return flag ==
"0" || flag ==
"1"; });
1423 throw std::runtime_error(
"QE ATOMIC_POSITIONS line '" + line +
"' has '" +
1425 "' after its coordinates, which is not three if_pos flags "
1427 return {trailing_tokens[0] ==
"1", trailing_tokens[1] ==
"1", trailing_tokens[2] ==
"1"};
1444 static void refuse_dftd3_defaults_q3_does_not_implement(
bool has_dftd3_version,
1445 bool has_dftd3_threebody) {
1446 std::string explanation;
1447 std::string lines_to_add;
1448 if (!has_dftd3_version) {
1449 explanation +=
" The input does not state dftd3_version, so pw.x uses its default, "
1450 "dftd3_version = 3, which is zero damping.";
1451 lines_to_add +=
"dftd3_version = 4";
1453 if (!has_dftd3_threebody) {
1454 explanation +=
" The input does not state dftd3_threebody, so pw.x uses its default, "
1455 "dftd3_threebody = .true., which adds the three-body term.";
1457 std::string(lines_to_add.empty() ?
"" :
" and ") +
"dftd3_threebody = .false.";
1459 if (lines_to_add.empty())
1461 throw std::runtime_error(
"QE input selects DFT-D3 with vdw_corr, and Q3 implements only "
1462 "its two-body term with Becke-Johnson damping." +
1463 explanation +
" To run two-body D3(BJ) in both codes, add " +
1464 lines_to_add +
" to &SYSTEM.");
1468 static size_t first_unquoted_position(
const std::string& line,
char target) {
1470 for (
size_t position = 0; position < line.size(); position++) {
1471 char character = line[position];
1472 if (quote !=
'\0') {
1473 if (character == quote)
1475 }
else if (character ==
'\'' || character ==
'"') {
1477 }
else if (character == target) {
1481 return std::string::npos;
1485 static std::string strip_comment(
const std::string& line) {
1486 return line.substr(0, first_unquoted_position(line,
'!'));
1501 static bool strip_namelist_terminator(std::string& line,
const std::string& section) {
1502 size_t slash_position = first_unquoted_position(line,
'/');
1503 if (slash_position == std::string::npos)
1505 std::string after_slash = trim(line.substr(slash_position + 1));
1506 if (!after_slash.empty())
1507 throw std::runtime_error(
"QE namelist line '" + trim(line) +
"' has '" + after_slash +
1508 "' after the '/' that closes &" + section +
1509 ". Fortran stops reading the namelist at that slash and "
1510 "skips the rest of the line, so Q3 refuses the line rather "
1511 "than drop that text without a word.");
1512 line = line.substr(0, slash_position);
1525 static std::vector<std::pair<std::string, std::string>>
1526 namelist_keys_on_line(
const std::string& line) {
1527 std::vector<std::pair<std::string, std::string>> keys;
1528 std::vector<std::string> fragments;
1529 std::string current;
1536 int parenthesis_depth = 0;
1537 for (
char character : line) {
1538 if (quote !=
'\0') {
1539 if (character == quote)
1541 }
else if (character ==
'\'' || character ==
'"') {
1543 }
else if (character ==
'(') {
1544 parenthesis_depth++;
1545 }
else if (character ==
')') {
1546 parenthesis_depth = std::max(0, parenthesis_depth - 1);
1547 }
else if (character ==
',' && parenthesis_depth == 0) {
1548 fragments.push_back(current);
1552 current += character;
1554 fragments.push_back(current);
1556 for (
const std::string& fragment : fragments) {
1557 std::string trimmed_fragment = trim(fragment);
1558 if (trimmed_fragment.empty())
1560 size_t equals_position = trimmed_fragment.find(
'=');
1561 if (equals_position == std::string::npos)
1562 throw std::runtime_error(
1563 "QE namelist entry '" + trimmed_fragment +
1564 "' is not an assignment. Fortran's array-continuation form "
1565 "(`starting_magnetization = 0.5, 0.3`) is not read; give each element its "
1566 "own indexed assignment.");
1567 std::string key = to_lower(trim(trimmed_fragment.substr(0, equals_position)));
1568 size_t parenthesis_position = key.find(
'(');
1569 if (parenthesis_position != std::string::npos)
1570 key = key.substr(0, parenthesis_position);
1574 to_lower(strip_quotes(trim(trimmed_fragment.substr(equals_position + 1)))));
1586 static bool is_default_value(
const std::string& default_token,
const std::string& value) {
1587 if (default_token ==
".false.")
1588 return !fortran_logical_is_true(value);
1590 size_t consumed = 0;
1591 Real default_number = std::stod(default_token, &consumed);
1592 if (consumed == default_token.size())
1593 return std::abs(fortran_to_double(value) - default_number) < 1e-9;
1594 }
catch (
const std::exception&) {
1597 return value == default_token;
1604 static void classify_namelist_keys(
const std::string& line,
const std::string& section) {
1606 for (
const auto& [key, value] : namelist_keys_on_line(line)) {
1607 if (honoured.count(key) != 0)
1610 log::info(
" QE input: {} accepted and ignored (does not affect results)", key);
1615 log::info(
" QE input: {} is not used ({}); it changes how a relaxation "
1616 "reaches its minimum, not the minimum",
1618 unused_ionic_step->second);
1628 is_default_value(inert->second, value))
1632 throw std::runtime_error(
"QE namelist key '" + key +
"' in &" + section +
1633 " is not supported: " + unimplemented->second +
1634 ". Remove it or use Q3's own input format.");
1641 if (other_section !=
section && other_keys.count(key) != 0)
1642 throw std::runtime_error(
"QE namelist key '" + key +
"' belongs to &" +
1643 other_section +
", not &" +
section);
1644 throw std::runtime_error(
1645 "QE namelist key '" + key +
"' in &" + section +
1646 " is not recognized. Q3 refuses unknown keys rather than ignoring them, because "
1647 "ignoring one that changes physics silently produces a result that does not "
1648 "match the input.");
1658 static void apply_crystallographic_constants(InputData& data,
1665 if (length_a <= 0.0)
1666 throw std::runtime_error(
"QE input gives A = " + std::to_string(length_a) +
1667 "; the lattice parameter A must be positive");
1668 if (data.cell_dimension_1 > 0.0)
1669 throw std::runtime_error(
"QE input gives both celldm(1) and A; they are alternative "
1670 "spellings of the same quantity and QE refuses the pair too");
1673 data.cell_dimension_2 = (length_b > 0.0) ? length_b / length_a : 0.0;
1674 data.cell_dimension_3 = (length_c > 0.0) ? length_c / length_a : 0.0;
1676 int index = data.bravais_lattice_index;
1677 if (index == 14 || index == 0) {
1678 data.cell_dimension_4 = cosine_bc;
1679 data.cell_dimension_5 = cosine_ac;
1680 data.cell_dimension_6 = cosine_ab;
1681 }
else if (index == -12 || index == -13) {
1682 data.cell_dimension_5 = cosine_ac;
1683 }
else if (index == 5 || index == -5 || index == 12 || index == 13) {
1684 data.cell_dimension_4 = cosine_ab;
1689 static bool fortran_logical_is_true(
const std::string& value) {
1690 std::string lower = to_lower(trim(strip_quotes(value)));
1691 return lower ==
".true." || lower ==
"t" || lower ==
".t." || lower ==
"true";
1703 static bool is_whole_key_at(
const std::string& lower_line,
size_t position,
size_t length) {
1705 char before = lower_line[position - 1];
1706 if (before !=
' ' && before !=
'\t' && before !=
',')
1709 size_t after_position = position + length;
1710 if (after_position >= lower_line.size())
1712 char after = lower_line[after_position];
1713 return after ==
'=' || after ==
' ' || after ==
'\t' || after ==
'(';
1718 extract_key_value(
const std::string& line,
const std::string& key, std::string& value) {
1719 std::string lower_line = to_lower(line);
1720 std::string lower_key = to_lower(key);
1721 size_t position = lower_line.find(lower_key);
1722 while (position != std::string::npos &&
1723 !is_whole_key_at(lower_line, position, lower_key.size()))
1724 position = lower_line.find(lower_key, position + 1);
1725 if (position == std::string::npos)
1728 size_t equals_position = line.find(
'=', position);
1729 if (equals_position == std::string::npos)
1732 size_t start = equals_position + 1;
1733 while (start < line.size() && line[start] ==
' ')
1735 size_t end = line.find_first_of(
",!\n\r", start);
1736 if (end == std::string::npos)
1738 value = trim(line.substr(start, end - start));
1739 value = strip_quotes(value);