96 int maximum_shell_count = 12,
97 Real shell_relative_tolerance = 1e-6,
98 Real singular_value_tolerance = 1e-10,
99 Real completeness_tolerance = 1e-10) {
100 neighbor_directions_.clear();
101 number_of_shells_ = 0;
103 const std::array<int, 3> grid_dimensions = {
104 grid_dimension_x, grid_dimension_y, grid_dimension_z};
106 std::vector<Shell> candidate_shells = enumerate_candidate_shells(
107 reciprocal_lattice_vectors, grid_dimensions, search_range, shell_relative_tolerance);
113 std::vector<std::array<Real, 6>> accepted_shell_tensors;
114 std::vector<const Shell*> accepted_shells;
115 const std::array<Real, 6> target = {1.0, 1.0, 1.0, 0.0, 0.0, 0.0};
117 for (
const Shell& candidate : candidate_shells) {
118 if (
static_cast<int>(accepted_shells.size()) >= maximum_shell_count) {
122 std::array<Real, 6> candidate_tensor = shell_tensor(candidate);
123 accepted_shell_tensors.push_back(candidate_tensor);
124 accepted_shells.push_back(&candidate);
126 LeastSquaresSolution solution =
127 solve_least_squares(accepted_shell_tensors, target, singular_value_tolerance);
129 if (!solution.smallest_singular_value_cleared_tolerance) {
135 accepted_shell_tensors.pop_back();
136 accepted_shells.pop_back();
140 if (solution.residual_norm < completeness_tolerance) {
141 finalize(accepted_shells, solution.weights);
146 throw std::runtime_error(
"KPointNeighborShells::compute: no set of shells up to " +
147 std::to_string(maximum_shell_count) +
" shells (search range " +
148 std::to_string(search_range) +
149 ") satisfies the completeness condition to the requested "
150 "tolerance -- widen search_range or maximum_shell_count.");
154 return neighbor_directions_;
158 return number_of_shells_;
173 const std::array<int, 3>& grid_dimensions) {
174 const int dimension_x = grid_dimensions[0];
175 const int dimension_y = grid_dimensions[1];
176 const int dimension_z = grid_dimensions[2];
178 const int index_z = starting_kpoint_index % dimension_z;
179 const int index_y = (starting_kpoint_index / dimension_z) % dimension_y;
180 const int index_x = starting_kpoint_index / (dimension_y * dimension_z);
182 return wrap_index(index_x + grid_step.
x, dimension_x) * dimension_y * dimension_z +
183 wrap_index(index_y + grid_step.
y, dimension_y) * dimension_z +
184 wrap_index(index_z + grid_step.
z, dimension_z);
200 const std::array<int, 3>& grid_dimensions,
201 const std::vector<KPoint>& physical_kpoints) {
206 const Vector3D& starting_coordinates = physical_kpoints[starting_kpoint_index].coordinates;
207 const Vector3D& neighbor_coordinates =
210 for (
int component = 0; component < 3; component++) {
212 starting_coordinates[component] +
static_cast<Real>(grid_step[component]) /
213 static_cast<Real>(grid_dimensions[component]);
214 const Real difference = target - neighbor_coordinates[component];
215 const int shift_component =
static_cast<int>(std::lround(difference));
217 if (std::abs(difference -
static_cast<Real>(shift_component)) > 1e-6) {
218 throw std::runtime_error(
219 "KPointNeighborShells::resolve_neighbor: the k-point list does not "
220 "enumerate a full, unreduced Monkhorst-Pack grid with the given "
221 "dimensions in the expected order (first index slowest, third fastest, "
222 "as KPointsGrid and q3.workflows.interpolation.full_grid_kpoints produce) "
223 "-- the neighbor's coordinate does not differ from the target by an "
224 "integer number of reciprocal lattice vectors.");
234 std::vector<IntegerVector3D> grid_steps;
235 std::vector<Vector3D> translations;
236 Real magnitude_squared = 0.0;
239 struct LeastSquaresSolution {
240 std::vector<Real> weights;
241 Real residual_norm = 0.0;
242 bool smallest_singular_value_cleared_tolerance =
false;
245 static int wrap_index(
int raw_index,
int dimension) {
246 int wrapped = raw_index % dimension;
248 wrapped += dimension;
258 static std::array<Real, 6> shell_tensor(
const Shell& shell) {
259 std::array<Real, 6> tensor = {0, 0, 0, 0, 0, 0};
260 for (
const Vector3D& translation : shell.translations) {
261 tensor[0] += translation.x * translation.x;
262 tensor[1] += translation.y * translation.y;
263 tensor[2] += translation.z * translation.z;
264 tensor[3] += translation.x * translation.y;
265 tensor[4] += translation.x * translation.z;
266 tensor[5] += translation.y * translation.z;
278 static std::vector<Shell>
279 enumerate_candidate_shells(
const Matrix3x3& reciprocal_lattice_vectors,
280 const std::array<int, 3>& grid_dimensions,
282 Real shell_relative_tolerance) {
284 IntegerVector3D grid_step;
285 Vector3D translation;
286 Real magnitude_squared;
289 std::vector<Candidate> candidates;
290 for (
int step_x = -search_range; step_x <= search_range; step_x++) {
291 for (
int step_y = -search_range; step_y <= search_range; step_y++) {
292 for (
int step_z = -search_range; step_z <= search_range; step_z++) {
293 if (step_x == 0 && step_y == 0 && step_z == 0) {
296 Vector3D translation = (
static_cast<Real>(step_x) / grid_dimensions[0]) *
297 reciprocal_lattice_vectors[0] +
298 (
static_cast<Real>(step_y) / grid_dimensions[1]) *
299 reciprocal_lattice_vectors[1] +
300 (
static_cast<Real>(step_z) / grid_dimensions[2]) *
301 reciprocal_lattice_vectors[2];
302 candidates.push_back(
303 {{step_x, step_y, step_z}, translation, translation.norm2()});
315 std::stable_sort(candidates.begin(),
318 return left.magnitude_squared < right.magnitude_squared;
321 std::vector<Shell> shells;
322 for (
const Candidate& candidate : candidates) {
323 if (!shells.empty() &&
324 std::abs(candidate.magnitude_squared - shells.back().magnitude_squared) <=
325 shell_relative_tolerance * shells.back().magnitude_squared) {
326 shells.back().grid_steps.push_back(candidate.grid_step);
327 shells.back().translations.push_back(candidate.translation);
331 shell.grid_steps.push_back(candidate.grid_step);
332 shell.translations.push_back(candidate.translation);
333 shell.magnitude_squared = candidate.magnitude_squared;
334 shells.push_back(std::move(shell));
349 static LeastSquaresSolution
350 solve_least_squares(
const std::vector<std::array<Real, 6>>& shell_tensors,
351 const std::array<Real, 6>& target,
352 Real singular_value_tolerance) {
354 const int number_of_equations = 6;
360 std::vector<Real> matrix_a(
static_cast<std::size_t
>(number_of_equations) *
363 for (
int component = 0; component < number_of_equations; component++) {
364 matrix_a[shell_index * number_of_equations + component] =
365 shell_tensors[shell_index][component];
369 const int minimum_dimension = std::min(number_of_equations,
number_of_shells);
370 std::vector<Real> singular_values(minimum_dimension);
371 std::vector<Real> left_singular_vectors(
static_cast<std::size_t
>(number_of_equations) *
372 number_of_equations);
373 std::vector<Real> right_singular_vectors_transposed(
376 std::vector<Real> matrix_a_copy = matrix_a;
377 int workspace_size = -1;
378 Real optimal_workspace_size = 0.0;
383 &number_of_equations,
385 matrix_a_copy.data(),
386 &number_of_equations,
387 singular_values.data(),
388 left_singular_vectors.data(),
389 &number_of_equations,
390 right_singular_vectors_transposed.data(),
392 &optimal_workspace_size,
396 throw std::runtime_error(
397 "KPointNeighborShells: DGESVD workspace query failed with info = " +
398 std::to_string(info));
401 workspace_size =
static_cast<int>(optimal_workspace_size);
402 std::vector<Real> workspace(workspace_size);
403 matrix_a_copy = matrix_a;
407 &number_of_equations,
409 matrix_a_copy.data(),
410 &number_of_equations,
411 singular_values.data(),
412 left_singular_vectors.data(),
413 &number_of_equations,
414 right_singular_vectors_transposed.data(),
420 throw std::runtime_error(
"KPointNeighborShells: DGESVD failed with info = " +
421 std::to_string(info));
424 const Real largest_singular_value = singular_values.empty() ? 0.0 : singular_values[0];
425 const Real smallest_singular_value =
426 singular_values.empty() ? 0.0 : singular_values[minimum_dimension - 1];
428 LeastSquaresSolution solution;
429 solution.smallest_singular_value_cleared_tolerance =
431 (smallest_singular_value >
432 singular_value_tolerance * std::max(1.0, largest_singular_value));
442 std::vector<Real> u_transpose_target(number_of_equations, 0.0);
443 for (
int singular_index = 0; singular_index < number_of_equations; singular_index++) {
445 for (
int equation_row = 0; equation_row < number_of_equations; equation_row++) {
446 sum += left_singular_vectors[singular_index * number_of_equations + equation_row] *
447 target[equation_row];
449 u_transpose_target[singular_index] = sum;
463 for (
int singular_index = 0; singular_index < minimum_dimension; singular_index++) {
464 if (singular_values[singular_index] <=
465 singular_value_tolerance * std::max(1.0, largest_singular_value)) {
470 u_transpose_target[singular_index] / singular_values[singular_index];
472 weights[shell_index] = sum;
475 Real residual_squared = 0.0;
476 for (
int component = 0; component < number_of_equations; component++) {
477 Real predicted = 0.0;
479 predicted += shell_tensors[shell_index][component] * weights[shell_index];
481 Real component_residual = predicted - target[component];
482 residual_squared += component_residual * component_residual;
485 solution.weights = std::move(weights);
486 solution.residual_norm = std::sqrt(residual_squared);
490 void finalize(
const std::vector<const Shell*>& accepted_shells,
491 const std::vector<Real>& weights) {
492 for (
int shell_index = 0; shell_index < static_cast<int>(accepted_shells.size());
494 const Shell& shell = *accepted_shells[shell_index];
495 const Real weight = weights[shell_index];
496 if (weight < -1e-8) {
497 throw std::runtime_error(
498 "KPointNeighborShells::compute: the least-squares solve produced a "
499 "negative shell weight (" +
500 std::to_string(weight) +
501 ") -- the completeness condition has no physically valid solution "
502 "with the shells found; widen search_range.");
504 for (std::size_t direction_index = 0; direction_index < shell.grid_steps.size();
506 neighbor_directions_.push_back({shell.grid_steps[direction_index],
507 shell.translations[direction_index],
511 number_of_shells_ =
static_cast<int>(accepted_shells.size());
514 std::vector<KPointNeighborDirection> neighbor_directions_;
515 int number_of_shells_ = 0;