74 log::error(
"Usage: q3_utility volumetric <input.yml> [--field <name>] [--format <name>] "
75 "[--spin <n>] [--kpoint <k>] [--band <n>] [--outdir <dir>] [--output <file>]");
76 log::error(
" <input.yml> the manifest the run used; its control.outdir/prefix name the "
77 "save directory, and its cell and cutoffs the grid");
78 log::error(
" --field density (default), potential, or wavefunction");
79 log::error(
" --format {} (cube is the default)",
81 log::error(
" --spin spin channel, 1-based (default 1)");
82 log::error(
" --kpoint which wfc<k>.dat to read, 1-based (default 1)");
83 log::error(
" --band which band of it, 1-based (default 1)");
84 log::error(
" --outdir where to write (default: the run's own outdir)");
85 log::error(
" --output the full output path, overriding --outdir and the default "
90 static int run(
const std::vector<std::string>& arguments) {
91 std::string input_file;
92 std::string field_name =
"density";
93 std::string format =
"cube";
94 std::string output_directory;
95 std::string output_file;
97 int kpoint_number = 1;
100 for (std::size_t index = 0; index < arguments.size(); index++) {
101 const std::string& argument = arguments[index];
102 auto take_value = [&](
const char* flag) -> std::string {
103 if (index + 1 >= arguments.size())
104 throw std::runtime_error(std::string(flag) +
" needs a value");
105 return arguments[++index];
107 if (argument ==
"--field") {
108 field_name = take_value(
"--field");
109 }
else if (argument ==
"--format") {
110 format = take_value(
"--format");
111 }
else if (argument ==
"--spin") {
112 spin_channel = parse_positive_integer(
"--spin", take_value(
"--spin"));
113 }
else if (argument ==
"--kpoint") {
114 kpoint_number = parse_positive_integer(
"--kpoint", take_value(
"--kpoint"));
115 }
else if (argument ==
"--band") {
116 band_number = parse_positive_integer(
"--band", take_value(
"--band"));
117 }
else if (argument ==
"--outdir") {
118 output_directory = take_value(
"--outdir");
119 }
else if (argument ==
"--output") {
120 output_file = take_value(
"--output");
121 }
else if (argument ==
"-h" || argument ==
"--help") {
124 }
else if (argument.rfind(
"-", 0) == 0) {
128 }
else if (input_file.empty()) {
129 input_file = argument;
131 log::error(
"Unexpected extra argument '{}'", argument);
136 if (input_file.empty()) {
143 if (!std::filesystem::is_directory(save_directory)) {
144 throw std::runtime_error(
145 "no save directory at '" + save_directory +
146 "'. It is written by the run this input describes; point control.outdir and "
147 "control.prefix at a run that has finished.");
150 const Crystal crystal = build_crystal(input);
151 const std::array<int, 3> grid_dimensions = charge_density_grid_dimensions(input, crystal);
152 log::info(
"Reading {} on a {}x{}x{} grid from {}",
168 std::string path = output_file;
170 const std::string directory =
172 std::filesystem::create_directories(directory);
173 path = directory +
"/" + writer->default_file_name(input.
prefix +
"_" + field_name);
175 writer->write(path, field);
183 log::info(
" integral over the cell: {:.8f} {} (cell volume {:.6f} bohr^3)",
191 static int parse_positive_integer(
const char* flag,
const std::string& text) {
192 std::size_t consumed = 0;
195 value = std::stoi(text, &consumed);
196 }
catch (
const std::exception&) {
199 if (consumed != text.size() || value <= 0)
200 throw std::runtime_error(std::string(flag) +
" expects a positive integer, got '" +
215 static Crystal build_crystal(
const InputData& input) {
216 std::vector<AtomSpecies> species = input.species;
219 }
catch (
const std::exception& error) {
220 log::warn(
"Could not read this run's pseudopotentials ({}); the atoms' valence "
221 "charges will be written as zero.",
223 species = input.species;
229 static std::array<int, 3> charge_density_grid_dimensions(
const InputData& input,
230 const Crystal& crystal) {
231 const Real cutoff = input.charge_density_energy_cutoff > 0.0
232 ? input.charge_density_energy_cutoff
233 : 4.0 * input.wavefunction_energy_cutoff;
236 std::array<int, 3> dimensions = {0, 0, 0};
238 crystal.two_pi_over_lattice_parameter_squared(),
239 crystal.lattice_vectors(),
240 crystal.reciprocal_lattice_vectors(),
249 static VolumetricField empty_field(
const Crystal& crystal,
250 const std::array<int, 3>& grid_dimensions) {
251 VolumetricField field;
252 field.grid_dimensions = grid_dimensions;
253 field.lattice_vectors_bohr = crystal.lattice_vectors_bohr();
254 for (
const auto& species : crystal.species()) {
255 field.species_symbols.push_back(species.symbol);
256 field.species_valence_charges.push_back(species.valence_charge);
258 for (
int atom_index = 0; atom_index < crystal.number_of_atoms(); atom_index++) {
259 field.atom_species_indices.push_back(crystal.species_type_index(atom_index));
260 field.atom_positions_crystal.push_back(crystal.fractional_position(atom_index));
265 static VolumetricField build_field(
const std::string& field_name,
266 const std::string& save_directory,
267 const Crystal& crystal,
268 const std::array<int, 3>& grid_dimensions,
272 if (field_name ==
"density")
273 return build_density(save_directory, crystal, grid_dimensions, spin_channel);
274 if (field_name ==
"potential")
275 return build_potential(save_directory, crystal, grid_dimensions, spin_channel);
276 if (field_name ==
"wavefunction") {
277 return build_band_probability_density(
278 save_directory, crystal, grid_dimensions, kpoint_number, band_number);
280 throw std::runtime_error(
"Unknown field '" + field_name +
281 "'. Supported: density, potential, wavefunction");
284 static VolumetricField build_density(
const std::string& save_directory,
285 const Crystal& crystal,
286 const std::array<int, 3>& grid_dimensions,
288 const std::string path = save_directory +
"/charge-density.dat";
289 if (!std::filesystem::exists(path))
290 throw std::runtime_error(
"no charge density at '" + path +
"'.");
291 QEPlanewaveKohnShamChargeDensityReader reader;
293 if (spin_channel > reader.number_of_spin_components()) {
294 throw std::runtime_error(
"'" + path +
"' holds " +
295 std::to_string(reader.number_of_spin_components()) +
296 " spin channel(s); --spin " + std::to_string(spin_channel) +
297 " asks for one that is not there.");
300 require_grid_holds(reader.miller_indices(), grid_dimensions, path);
302 VolumetricField field = empty_field(crystal, grid_dimensions);
304 reader.miller_indices(),
305 reader.charge_density_g()[spin_channel - 1],
306 reader.is_gamma_only());
307 field.description =
"charge density";
308 field.unit =
"e/bohr^3";
309 field.is_a_density =
true;
313 static VolumetricField build_potential(
const std::string& save_directory,
314 const Crystal& crystal,
315 const std::array<int, 3>& grid_dimensions,
317 const std::string path = save_directory +
"/effective-potential.q3cp";
318 if (!std::filesystem::exists(path))
319 throw std::runtime_error(
"no effective potential at '" + path +
"'.");
320 KohnShamQuantumSystemSnapshot snapshot;
321 Q3PlanewaveKohnShamQuantumSystemCheckpoint checkpoint;
322 if (!checkpoint.load(path, snapshot))
323 throw std::runtime_error(
"'" + path +
"' could not be read as a Q3 checkpoint.");
324 if (spin_channel >
static_cast<int>(snapshot.effective_potential_real.size())) {
325 throw std::runtime_error(
326 "'" + path +
"' holds " + std::to_string(snapshot.effective_potential_real.size()) +
327 " potential channel(s); --spin " + std::to_string(spin_channel) +
328 " asks for one that is not there.");
331 VolumetricField field = empty_field(crystal, grid_dimensions);
332 field.values = snapshot.effective_potential_real[spin_channel - 1];
333 require_grid_size(field, path,
"effective potential");
334 field.description =
"effective potential";
350 static VolumetricField build_band_probability_density(
const std::string& save_directory,
351 const Crystal& crystal,
352 const std::array<int, 3>& grid_dimensions,
355 const std::string path = save_directory +
"/wfc" + std::to_string(kpoint_number) +
".dat";
356 if (!std::filesystem::exists(path)) {
357 throw std::runtime_error(
"no wavefunction at '" + path +
358 "'. The save directory holds one wfc<k>.dat per k-point of "
359 "the run's irreducible wedge.");
361 QEPlanewaveKohnShamWavefunctionReader reader;
363 if (band_number > reader.number_of_bands()) {
364 throw std::runtime_error(
"'" + path +
"' holds " +
365 std::to_string(reader.number_of_bands()) +
366 " band(s); --band " + std::to_string(band_number) +
367 " asks for one that is not there.");
370 require_grid_holds(reader.miller_indices(), grid_dimensions, path);
372 const std::vector<Complex> amplitude =
374 reader.miller_indices(),
375 reader.band_coefficients(band_number - 1),
376 reader.is_gamma_only());
377 VolumetricField field = empty_field(crystal, grid_dimensions);
378 const Real inverse_volume = 1.0 / crystal.cell_volume();
379 field.values.resize(amplitude.size());
380 for (std::size_t index = 0; index < amplitude.size(); index++)
381 field.values[index] = std::norm(amplitude[index]) * inverse_volume;
382 field.description =
"band " + std::to_string(band_number) +
383 " probability density at "
385 std::to_string(kpoint_number);
386 field.unit =
"e/bohr^3";
387 field.is_a_density =
true;
398 static void require_grid_holds(
const std::vector<IntegerVector3D>& miller_indices,
399 const std::array<int, 3>& grid_dimensions,
400 const std::string& path) {
401 std::array<int, 3> extent = {0, 0, 0};
402 for (
const IntegerVector3D& miller : miller_indices) {
403 extent[0] = std::max(extent[0], std::abs(miller.x));
404 extent[1] = std::max(extent[1], std::abs(miller.y));
405 extent[2] = std::max(extent[2], std::abs(miller.z));
407 for (
int axis = 0; axis < 3; axis++) {
408 if (extent[axis] <= grid_dimensions[axis] / 2)
410 throw std::runtime_error(
411 "'" + path +
"' reaches Miller index " + std::to_string(extent[axis]) +
412 " along axis " + std::to_string(axis + 1) +
", which a " +
413 std::to_string(grid_dimensions[0]) +
"x" + std::to_string(grid_dimensions[1]) +
414 "x" + std::to_string(grid_dimensions[2]) +
415 " grid cannot hold. This input's cell and cutoffs do not describe the run that "
425 static void require_grid_size(
const VolumetricField& field,
426 const std::string& path,
427 const std::string& what) {
428 if (
static_cast<int>(field.values.size()) == field.number_of_grid_points())
430 throw std::runtime_error(
431 "the " + what +
" in '" + path +
"' has " + std::to_string(field.values.size()) +
432 " grid points, but this input describes a " + std::to_string(field.grid_dimensions[0]) +
433 "x" + std::to_string(field.grid_dimensions[1]) +
"x" +
434 std::to_string(field.grid_dimensions[2]) +
" grid (" +
435 std::to_string(field.number_of_grid_points()) +
436 " points). The input and the save directory are from different runs.");