Loading...
Searching...
No Matches
xsf.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * XsfVolumetricFieldWriter — XCrySDen's XSF format, structure plus one 3D
5 * datagrid.
6 *
7 * Written from the published XSF specification (the XCrySDen documentation's
8 * "XSF (XCrySDen Structure File)" chapter); no reference implementation was
9 * consulted. The file is a `CRYSTAL` block — `PRIMVEC` and `PRIMCOORD` — and
10 * then a `BEGIN_BLOCK_DATAGRID_3D` carrying one `BEGIN_DATAGRID_3D_*` grid.
11 *
12 * **Conventions this writer chose, and the reasons.**
13 *
14 * - *Ångström.* XSF's lengths are Ångström by definition, unlike the cube
15 * format's bohr, so the cell and the atoms are converted here and only
16 * here. The datagrid *values* are not converted — they stay in the field's
17 * own unit, which the datagrid's name states, because XCrySDen only ever
18 * draws isosurfaces of them and a silently rescaled density is worse than
19 * a labelled one.
20 * - *The first axis varies fastest*, which is the opposite of the cube
21 * format and is what the specification requires: the grid is written as
22 * Fortran would write `(((v(i,j,k), i=1,nx), j=1,ny), k=1,nz)`.
23 * `tests/unit/io/volumetric/test_xsf.cpp` pins it against a field whose
24 * value identifies its own grid point.
25 * - *The boundary plane is duplicated.* An XSF "general" datagrid spans the
26 * three vectors it names including their far end, so a periodic n-point
27 * grid is written as n+1 points per axis with the last a copy of the
28 * first. Writing n points instead leaves a one-voxel gap at the cell
29 * boundary that XCrySDen renders as a hole through every isosurface.
30 */
31
32#include <fstream>
33#include <iomanip>
34#include <stdexcept>
35#include <string>
36
40
41namespace q3 {
42
44 public:
45 void write(const std::string& path, const VolumetricField& field) const override {
46 std::ofstream stream(path);
47 if (!stream)
48 throw std::runtime_error("Cannot write xsf file: " + path);
49
50 const Real bohr_to_angstrom = constants::BOHR_RADIUS_ANGSTROM;
51 stream << "# Q3 " << field.description << " (" << field.unit << ")\n";
52 stream << "CRYSTAL\n";
53 stream << "PRIMVEC\n";
54 stream << std::fixed << std::setprecision(10);
55 for (int axis = 0; axis < 3; axis++) {
56 for (int component = 0; component < 3; component++)
57 stream << std::setw(20)
58 << field.lattice_vectors_bohr[axis][component] * bohr_to_angstrom;
59 stream << "\n";
60 }
61 stream << "PRIMCOORD\n";
62 stream << field.number_of_atoms() << " 1\n";
63 for (int atom_index = 0; atom_index < field.number_of_atoms(); atom_index++) {
64 const Vector3D position = field.cartesian_position_bohr(atom_index);
65 stream << std::setw(4) << field.atomic_number(atom_index);
66 for (int component = 0; component < 3; component++)
67 stream << std::setw(20) << position[component] * bohr_to_angstrom;
68 stream << "\n";
69 }
70
71 const auto& dimensions = field.grid_dimensions;
72 stream << "BEGIN_BLOCK_DATAGRID_3D\n";
73 stream << " Q3_" << datagrid_name(field) << "\n";
74 stream << " BEGIN_DATAGRID_3D_Q3_" << datagrid_name(field) << "\n";
75 stream << " " << dimensions[0] + 1 << " " << dimensions[1] + 1 << " " << dimensions[2] + 1
76 << "\n";
77 stream << " " << std::setw(20) << 0.0 << std::setw(20) << 0.0 << std::setw(20) << 0.0
78 << "\n";
79 for (int axis = 0; axis < 3; axis++) {
80 stream << " ";
81 for (int component = 0; component < 3; component++)
82 stream << std::setw(20)
83 << field.lattice_vectors_bohr[axis][component] * bohr_to_angstrom;
84 stream << "\n";
85 }
86
87 stream << std::scientific << std::setprecision(10);
88 int values_on_line = 0;
89 for (int index_3 = 0; index_3 <= dimensions[2]; index_3++) {
90 for (int index_2 = 0; index_2 <= dimensions[1]; index_2++) {
91 for (int index_1 = 0; index_1 <= dimensions[0]; index_1++) {
92 stream << std::setw(18)
93 << field.value(index_1 % dimensions[0],
94 index_2 % dimensions[1],
95 index_3 % dimensions[2]);
96 if (++values_on_line == values_per_line) {
97 stream << "\n";
98 values_on_line = 0;
99 }
100 }
101 }
102 }
103 if (values_on_line != 0)
104 stream << "\n";
105
106 stream << " END_DATAGRID_3D\n";
107 stream << "END_BLOCK_DATAGRID_3D\n";
108
109 stream.flush();
110 if (!stream)
111 throw std::runtime_error("Failed while writing xsf file: " + path);
112 }
113
114 [[nodiscard]] std::string default_file_name(const std::string& stem) const override {
115 return stem + ".xsf";
116 }
117
118 private:
119 static constexpr int values_per_line = 6;
120
121 /**
122 * A datagrid identifier XCrySDen shows in its field chooser: no spaces, and
123 * carrying the unit, since the format has nowhere else to put it.
124 */
125 [[nodiscard]] static std::string datagrid_name(const VolumetricField& field) {
126 std::string name = field.description + "_" + field.unit;
127 for (char& character : name) {
128 const bool is_plain = (character >= 'a' && character <= 'z') ||
129 (character >= 'A' && character <= 'Z') ||
130 (character >= '0' && character <= '9');
131 if (!is_plain)
132 character = '_';
133 }
134 return name;
135 }
136};
137
138} // namespace q3
std::string default_file_name(const std::string &stem) const override
Definition xsf.hpp:114
void write(const std::string &path, const VolumetricField &field) const override
Write field to path, throwing std::runtime_error if it cannot be written.
Definition xsf.hpp:45
constexpr Real BOHR_RADIUS_ANGSTROM
Bohr radius in Ångströms (BOHR_RADIUS_SI * 1e10; BOHR_RADIUS_ANGSTROM)
double Real
Vector3D cartesian_position_bohr(int atom_index) const
Cartesian position of one atom, in bohr.
Definition field.hpp:111
int number_of_atoms() const
Definition field.hpp:78
std::string unit
The unit values is in, e.g. "e/bohr^3".
Definition field.hpp:60
Matrix3x3 lattice_vectors_bohr
Rows are a1, a2, a3, Cartesian, in bohr.
Definition field.hpp:44
std::string description
Free text for a format's comment line, e.g. "charge density".
Definition field.hpp:58
Real value(int index_1, int index_2, int index_3) const
The sample at grid point (i1, i2, i3), third index fastest.
Definition field.hpp:87
int atomic_number(int atom_index) const
Atomic number of one atom, from its element symbol.
Definition field.hpp:123
std::array< int, 3 > grid_dimensions
Grid point counts along a1, a2, a3.
Definition field.hpp:42