Loading...
Searching...
No Matches
convert_command.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * ConvertCommand — `q3_utility convert`: a saved record into an external format,
5 * or an external format into a saved record.
6 *
7 * Two records live in a run's `<prefix>.save/` directory today, and each has
8 * its own formats: the mean-field record (`mean-field.q3mf`, §4.8 of the IO
9 * reference) exports to BerkeleyGW, the interpolation record
10 * (`interpolation.q3ip`, §4.9) exports to the Wannier90 file set. Converting
11 * after the fact drives exactly the writer code the in-process export uses,
12 * so a second format, or a re-export after a format fix, costs a file read
13 * instead of a re-run -- Q3's analogue of Quantum ESPRESSO's `pw2bgw.x` and
14 * `pw2wannier90.x` in one place.
15 *
16 * q3_utility convert <save-directory> [--format berkeleygw|wannier90] [--outdir <dir>]
17 * q3_utility convert --from wannier90 --seedname <s> --grid nx ny nz [--outdir <dir>] [--prefix
18 * <p>]
19 *
20 * With no `--format`, the one format the directory's record allows is
21 * taken; a directory holding both records needs the flag. The import
22 * direction turns another code's `.amn`/`.eig`/`.nnkp` into an interpolation
23 * record that `q3_utility interpolate` then evaluates like a native one, which
24 * is how the engine is checked against Quantum ESPRESSO's matrices.
25 */
26
27#include <array>
28#include <filesystem>
29#include <stdexcept>
30#include <string>
31#include <vector>
32
37#include "io/logger.hpp"
40
41namespace q3 {
42
44 public:
45 static void print_usage() {
47 "Usage: q3_utility convert <save-directory> [--format <name>] [--outdir <directory>]");
49 " q3_utility convert --from wannier90 --seedname <s> --grid <nx> <ny> <nz> "
50 "[--outdir <directory>] [--prefix <p>]");
51 log::error(" <save-directory> a run's <prefix>.save directory, holding mean-field.q3mf "
52 "and/or interpolation.q3ip");
53 log::error(" --format berkeleygw (from the mean-field record) or wannier90 (from "
54 "the interpolation record);");
55 log::error(" defaults to the one format the directory's record allows");
56 log::error(" --outdir where to write (default: the save directory's parent, or . "
57 "for an import)");
58 log::error(" --from wannier90 import <seedname>.amn/.eig/.nnkp on --grid into "
59 "<outdir>/<prefix>.save/interpolation.q3ip");
60 log::error(" --prefix the run prefix an import is filed under (default: the "
61 "seedname's basename)");
62 }
63
64 /// Returns the process exit code; argument errors print usage, everything else throws.
65 static int run(const std::vector<std::string>& arguments) {
66 std::string save_directory;
67 std::string format;
68 std::string output_directory;
69 std::string from;
70 std::string seedname;
71 std::string prefix;
72 std::array<int, 3> grid = {0, 0, 0};
73 for (std::size_t index = 0; index < arguments.size(); index++) {
74 const std::string& argument = arguments[index];
75 auto take_value = [&](const char* flag) -> std::string {
76 if (index + 1 >= arguments.size()) {
77 throw std::runtime_error(std::string(flag) + " needs a value");
78 }
79 return arguments[++index];
80 };
81 if (argument == "--format") {
82 format = take_value("--format");
83 } else if (argument == "--outdir") {
84 output_directory = take_value("--outdir");
85 } else if (argument == "--from") {
86 from = take_value("--from");
87 } else if (argument == "--seedname") {
88 seedname = take_value("--seedname");
89 } else if (argument == "--prefix") {
90 prefix = take_value("--prefix");
91 } else if (argument == "--grid") {
92 for (int axis = 0; axis < 3; axis++) {
93 grid[axis] = parse_positive_integer("--grid", take_value("--grid"));
94 }
95 } else if (argument == "-h" || argument == "--help") {
97 return 0;
98 } else if (argument.rfind("-", 0) == 0) {
99 log::error("Unknown option '{}'", argument);
100 print_usage();
101 return 1;
102 } else if (save_directory.empty()) {
103 save_directory = argument;
104 } else {
105 log::error("Unexpected extra argument '{}'", argument);
106 print_usage();
107 return 1;
108 }
109 }
110
111 if (!from.empty()) {
112 return import_record(from, seedname, grid, output_directory, prefix);
113 }
114 if (save_directory.empty()) {
115 print_usage();
116 return 1;
117 }
118 return export_record(save_directory, format, output_directory);
119 }
120
121 private:
122 static int parse_positive_integer(const char* flag, const std::string& text) {
123 std::size_t consumed = 0;
124 int value = 0;
125 try {
126 value = std::stoi(text, &consumed);
127 } catch (const std::exception&) {
128 throw std::runtime_error(std::string(flag) + " expects positive integers, got '" +
129 text + "'");
130 }
131 if (consumed != text.size() || value <= 0) {
132 throw std::runtime_error(std::string(flag) + " expects positive integers, got '" +
133 text + "'");
134 }
135 return value;
136 }
137
138 static int import_record(const std::string& from,
139 const std::string& seedname,
140 const std::array<int, 3>& grid,
141 const std::string& output_directory,
142 const std::string& prefix) {
143 if (from != "wannier90") {
144 throw std::runtime_error("--from supports only wannier90, not '" + from + "'");
145 }
146 if (seedname.empty() || grid[0] <= 0) {
147 throw std::runtime_error(
148 "--from wannier90 needs --seedname <s> (the files are <s>.amn/.eig/.nnkp) and "
149 "--grid nx ny nz (a .nnkp lists k-points without saying what grid they came "
150 "from)");
151 }
152 const InterpolationRecord record = import_wannier90_files(seedname, grid);
153 const std::string prefix_name =
154 prefix.empty() ? std::filesystem::path(seedname).filename().string() : prefix;
155 const std::string directory =
156 output_directory.empty() ? std::string(".") : output_directory;
157 std::filesystem::create_directories(directory + "/" + prefix_name + ".save");
158 const std::string record_path = interpolation_record_path(directory, prefix_name);
159 write_interpolation_record(record, record_path);
160 log::info("Imported {} k-points, {} bands, {} Wannier functions from {}.amn/.eig/.nnkp "
161 "into {}",
162 record.wannier.number_of_kpoints(),
163 record.wannier.number_of_bands,
164 record.wannier.number_of_trial_orbitals,
165 seedname,
166 record_path);
167 return 0;
168 }
169
170 static int export_record(const std::string& save_directory,
171 std::string format,
172 std::string output_directory) {
173 /**
174 * `out/si.save/` (what shell completion produces) and `.` must name
175 * the same directory as `out/si.save`: with a trailing separator,
176 * filename() is empty and parent_path() is the save directory
177 * itself, which would put the export inside it and hand the prefix
178 * logic below the wrong path component.
179 */
180 std::filesystem::path save_path =
181 std::filesystem::absolute(save_directory).lexically_normal();
182 if (save_path.filename().empty()) {
183 save_path = save_path.parent_path();
184 }
185 const std::filesystem::path mean_field_path = save_path / "mean-field.q3mf";
186 const std::filesystem::path interpolation_path = save_path / "interpolation.q3ip";
187 const bool has_mean_field = std::filesystem::exists(mean_field_path);
188 const bool has_interpolation = std::filesystem::exists(interpolation_path);
189
190 if (format.empty()) {
191 if (has_mean_field && has_interpolation) {
192 throw std::runtime_error(
193 "'" + save_directory +
194 "' holds both a mean-field and an interpolation record; say which to "
195 "convert with --format berkeleygw or --format wannier90.");
196 }
197 if (has_mean_field) {
198 format = "berkeleygw";
199 } else if (has_interpolation) {
200 format = "wannier90";
201 } else {
202 /**
203 * Name the likely cause rather than just the missing files: each
204 * record is written only by a run that asked for it.
205 */
206 throw std::runtime_error(
207 "No record in '" + save_directory +
208 "': neither mean-field.q3mf (written by a "
209 "run with control.mean_field_export set to something other than 'none') nor "
210 "interpolation.q3ip (written by an NSCF run with a control.interpolation "
211 "block). Re-run that calculation with the export enabled to produce one.");
212 }
213 }
214
215 if (output_directory.empty()) {
216 const std::filesystem::path parent = save_path.parent_path();
217 output_directory = parent.empty() ? std::string(".") : parent.string();
218 }
219
220 if (format == "berkeleygw") {
221 if (!has_mean_field) {
222 throw std::runtime_error("No mean-field record at '" + mean_field_path.string() +
223 "'. It is written only by a run that set "
224 "control.mean_field_export to something other than "
225 "'none'; re-run that calculation with the export "
226 "enabled to produce it.");
227 }
228 const MeanFieldRecord record = read_mean_field_record(mean_field_path.string());
229 log::info("Read mean-field record: {} k-points, {} bands, {} G-vectors",
230 record.number_of_kpoints(),
231 record.number_of_bands,
232 record.number_of_g_vectors());
233 create_mean_field_exporter(format)->export_to_directory(record, output_directory);
234 log::info("Converted to '{}' in {}", format, output_directory);
235 return 0;
236 }
237 if (format == "wannier90") {
238 if (!has_interpolation) {
239 throw std::runtime_error("No interpolation record at '" +
240 interpolation_path.string() +
241 "'. It is written by an NSCF run with a "
242 "control.interpolation block; re-run that NSCF, or "
243 "import Wannier90 files with --from wannier90.");
244 }
245 const InterpolationRecord record =
246 read_interpolation_record(interpolation_path.string());
247 /// `<outdir>/<prefix>.save` -> the run's prefix, which names the exported files.
248 std::string prefix_name = save_path.filename().string();
249 const std::string suffix = ".save";
250 if (prefix_name.size() > suffix.size() &&
251 prefix_name.compare(prefix_name.size() - suffix.size(), suffix.size(), suffix) ==
252 0) {
253 prefix_name.erase(prefix_name.size() - suffix.size());
254 }
255 log::info("Read interpolation record ({}): {} k-points, {} bands",
256 record.method,
257 record.wannier.number_of_kpoints(),
258 record.wannier.number_of_bands);
259 create_interpolation_exporter(format)->export_to_directory(
260 record, output_directory, prefix_name);
261 log::info("Converted to '{}' in {}", format, output_directory);
262 return 0;
263 }
264 throw std::runtime_error("Unknown format '" + format +
265 "'. Supported: berkeleygw (from the mean-field record), "
266 "wannier90 (from the interpolation record)");
267 }
268};
269
270} // namespace q3
static int run(const std::vector< std::string > &arguments)
Returns the process exit code; argument errors print usage, everything else throws.
static void print_usage()
void error(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:192
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
InterpolationRecord import_wannier90_files(const std::string &seedname, const std::array< int, 3 > &grid)
Definition importer.hpp:42
void write_interpolation_record(const InterpolationRecord &record, const std::string &file_path)
std::string interpolation_record_path(const std::string &output_directory, const std::string &prefix)
Where a run with prefix in output_directory keeps its interpolation record.
std::unique_ptr< MeanFieldExporter > create_mean_field_exporter(const std::string &mean_field_export)
Definition factory.hpp:27
std::unique_ptr< InterpolationExporter > create_interpolation_exporter(const std::string &format)
Definition factory.hpp:21
InterpolationRecord read_interpolation_record(const std::string &file_path)
MeanFieldRecord read_mean_field_record(const std::string &file_path)
Loads a record written by write_mean_field_record(). Throws on any problem.