Loading...
Searching...
No Matches
libxc.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * LibXC-based exchange-correlation functional wrapper.
5 *
6 * Wraps the LibXC library to evaluate XC energy densities and potentials
7 * for LDA and GGA functionals. Supports both unpolarized (number_of_spin_components=1) and
8 * spin-polarized (number_of_spin_components=2) calculations.
9 *
10 * Density thresholds are matched to QE's behavior (1e-10) to ensure
11 * numerical agreement in energy comparisons.
12 */
13
14#include <algorithm>
15#include <cmath>
16#include <cstdio>
17#include <initializer_list>
18#include <stdexcept>
19#include <string>
20#include <utility>
21#include <vector>
22#include <xc.h>
23
27
28namespace q3 {
29
30/**
31 * LibXC-based functional. Wraps supported functionals from the LibXC library.
32 * Supports LDA and GGA families. Specified by exchange + correlation IDs.
33 * Can operate in both unpolarized (number_of_spin_components=1) and spin-polarized
34 * (number_of_spin_components=2) modes.
35 */
36class XC_LibXC : public XCFunctional {
37 public:
38 /**
39 * Construct from LibXC functional IDs.
40 * number_of_spin_components: 1=unpolarized, 2=spin-polarized (LSDA/SGGA)
41 */
42 XC_LibXC(int exchange_id,
43 int correlation_id,
45 std::string functional_identifier = "") {
46 exchange_id_ = exchange_id;
47 correlation_id_ = correlation_id;
48 number_of_spin_components_ = number_of_spin_components;
49
50 if (functional_identifier.empty()) {
51 functional_identifier =
52 "libxc:" + std::to_string(exchange_id) + ":" + std::to_string(correlation_id);
53 }
54
55 int libxc_spin_polarization =
56 (number_of_spin_components == 2) ? XC_POLARIZED : XC_UNPOLARIZED;
57
58 // Initialize exchange functional
59 if (xc_func_init(&func_x_, exchange_id, libxc_spin_polarization) != 0) {
60 throw std::runtime_error("LibXC: failed to initialize exchange functional id=" +
61 std::to_string(exchange_id));
62 }
63
64 // Initialize correlation functional
65 if (xc_func_init(&func_c_, correlation_id, libxc_spin_polarization) != 0) {
66 xc_func_end(&func_x_);
67 throw std::runtime_error("LibXC: failed to initialize correlation functional id=" +
68 std::to_string(correlation_id));
69 }
70
71 int exchange_family = func_x_.info->family;
72 int correlation_family = func_c_.info->family;
73
74 try {
75 ensure_supported_family(
76 functional_identifier, "exchange", exchange_id, func_x_, exchange_family);
77 ensure_supported_family(
78 functional_identifier, "correlation", correlation_id, func_c_, correlation_family);
79 } catch (...) {
80 xc_func_end(&func_c_);
81 xc_func_end(&func_x_);
82 throw;
83 }
84
85 exchange_requires_gradient_ = exchange_family == XC_FAMILY_GGA;
86 correlation_requires_gradient_ = correlation_family == XC_FAMILY_GGA;
87
88 // Set LibXC internal density thresholds to match QE's behavior.
89
90 // QE sets dens_threshold = 1e-10 ("small") for correlation
91
92 // and dens_threshold = grho_threshold_gga = 1e-10 for exchange.
93 constexpr double libxc_density_threshold = 1e-10;
94 xc_func_set_dens_threshold(&func_x_, libxc_density_threshold);
95 xc_func_set_dens_threshold(&func_c_, libxc_density_threshold);
96
97 functional_name_ = std::string(func_x_.info->name) + "+" + std::string(func_c_.info->name);
98 }
99
100 ~XC_LibXC() override {
101 xc_func_end(&func_x_);
102 xc_func_end(&func_c_);
103 }
104
105 [[nodiscard]] bool requires_density_gradient() const override {
106 return exchange_requires_gradient_ || correlation_requires_gradient_;
107 }
108
109 [[nodiscard]] int number_of_spin_components() const override {
110 return number_of_spin_components_;
111 }
112
113 [[nodiscard]] std::string name() const override {
114 return functional_name_;
115 }
116
117 // ─── Unpolarized LDA ────────────────────────────────────────────────
118 void compute(const std::vector<Real>& density,
119 std::vector<Real>& xc_potential,
120 std::vector<Real>& xc_energy_density) const override {
121 int number_of_grid_points = static_cast<int>(density.size());
122 xc_potential.resize(number_of_grid_points);
123 xc_energy_density.resize(number_of_grid_points);
124
125 std::vector<Real> exchange_energies(number_of_grid_points);
126 std::vector<Real> correlation_energies(number_of_grid_points);
127 std::vector<Real> exchange_potentials(number_of_grid_points);
128 std::vector<Real> correlation_potentials(number_of_grid_points);
129
130 std::vector<Real> absolute_density(number_of_grid_points);
131
132 // Cast-away const to pass to LibXC functions which take non-const xc_func_type*
133 auto* non_const_exchange = const_cast<xc_func_type*>(&func_x_);
134 auto* non_const_correlation = const_cast<xc_func_type*>(&func_c_);
135
136 for_each_grid_slice(number_of_grid_points, [&](int offset, int count) {
137 for (int grid_point_index = offset; grid_point_index < offset + count;
138 grid_point_index++) {
139 absolute_density[grid_point_index] = std::abs(density[grid_point_index]);
140 }
141 xc_lda_exc_vxc(non_const_exchange,
142 count,
143 absolute_density.data() + offset,
144 exchange_energies.data() + offset,
145 exchange_potentials.data() + offset);
146 xc_lda_exc_vxc(non_const_correlation,
147 count,
148 absolute_density.data() + offset,
149 correlation_energies.data() + offset,
150 correlation_potentials.data() + offset);
151 for (int grid_point_index = offset; grid_point_index < offset + count;
152 grid_point_index++) {
153 xc_energy_density[grid_point_index] =
155 (exchange_energies[grid_point_index] + correlation_energies[grid_point_index]);
156 xc_potential[grid_point_index] = constants::electron_charge_squared_rydberg *
157 (exchange_potentials[grid_point_index] +
158 correlation_potentials[grid_point_index]);
159 }
160 });
161
162 /**
163 * The finiteness check is a serial pass over the finished arrays
164 * rather than part of the slice body, because an exception that
165 * escapes a parallel region terminates the process. Evaluating every
166 * point before reporting changes nothing a caller can observe: the
167 * evaluation has no side effects, so the point reported and the
168 * numbers reported with it are the same either way.
169 */
170 for (int grid_point_index = 0; grid_point_index < number_of_grid_points;
171 grid_point_index++) {
172 if (!all_finite(
173 {xc_energy_density[grid_point_index], xc_potential[grid_point_index]})) {
174 throw_non_finite_output("unpolarized LDA",
175 grid_point_index,
176 {{"rho", absolute_density[grid_point_index]}});
177 }
178 }
179 }
180
181 // ─── Spin-polarized LDA (LSDA) ─────────────────────────────────────
182 void compute_spin(const std::vector<Real>& density_up,
183 const std::vector<Real>& density_down,
184 std::vector<Real>& xc_potential_up,
185 std::vector<Real>& xc_potential_down,
186 std::vector<Real>& xc_energy_density) const override {
187 int number_of_grid_points = static_cast<int>(density_up.size());
188 xc_potential_up.resize(number_of_grid_points);
189 xc_potential_down.resize(number_of_grid_points);
190 xc_energy_density.resize(number_of_grid_points);
191
192 std::vector<Real> density_array(2 * number_of_grid_points);
193 std::vector<Real> exchange_energies(number_of_grid_points);
194 std::vector<Real> correlation_energies(number_of_grid_points);
195 std::vector<Real> exchange_potentials(2 * number_of_grid_points);
196 std::vector<Real> correlation_potentials(2 * number_of_grid_points);
197
198 auto* non_const_exchange = const_cast<xc_func_type*>(&func_x_);
199 auto* non_const_correlation = const_cast<xc_func_type*>(&func_c_);
200
201 /**
202 * The spin arrays are interleaved two per grid point, so a slice that
203 * starts at grid point `offset` starts at element `2 * offset` in
204 * them. Slices stay whole grid points, so a pair is never split.
205 */
206 for_each_grid_slice(number_of_grid_points, [&](int offset, int count) {
207 for (int grid_point_index = offset; grid_point_index < offset + count;
208 grid_point_index++) {
209 density_array[2 * grid_point_index] = std::max(
210 density_up[grid_point_index], math::tolerances::minimum_electron_density);
211 density_array[2 * grid_point_index + 1] = std::max(
212 density_down[grid_point_index], math::tolerances::minimum_electron_density);
213 }
214 xc_lda_exc_vxc(non_const_exchange,
215 count,
216 density_array.data() + 2 * offset,
217 exchange_energies.data() + offset,
218 exchange_potentials.data() + 2 * offset);
219 xc_lda_exc_vxc(non_const_correlation,
220 count,
221 density_array.data() + 2 * offset,
222 correlation_energies.data() + offset,
223 correlation_potentials.data() + 2 * offset);
224 for (int grid_point_index = offset; grid_point_index < offset + count;
225 grid_point_index++) {
226 xc_energy_density[grid_point_index] =
228 (exchange_energies[grid_point_index] + correlation_energies[grid_point_index]);
229 xc_potential_up[grid_point_index] = constants::electron_charge_squared_rydberg *
230 (exchange_potentials[2 * grid_point_index] +
231 correlation_potentials[2 * grid_point_index]);
232 xc_potential_down[grid_point_index] =
234 (exchange_potentials[2 * grid_point_index + 1] +
235 correlation_potentials[2 * grid_point_index + 1]);
236 }
237 });
238
239 /// Checked serially, for the reason given in `compute()`.
240 for (int grid_point_index = 0; grid_point_index < number_of_grid_points;
241 grid_point_index++) {
242 if (!all_finite({xc_energy_density[grid_point_index],
243 xc_potential_up[grid_point_index],
244 xc_potential_down[grid_point_index]})) {
245 throw_non_finite_output("spin-polarized LDA",
246 grid_point_index,
247 {{"rho_up", density_array[2 * grid_point_index]},
248 {"rho_down", density_array[2 * grid_point_index + 1]}});
249 }
250 }
251 }
252
253 // ─── Unpolarized GGA ────────────────────────────────────────────────
254 void compute_gradient_corrected(const std::vector<Real>& density,
255 const std::vector<Real>& gradient_density_squared,
256 std::vector<Real>& xc_potential,
257 std::vector<Real>& gradient_correction_density,
258 std::vector<Real>& xc_energy_density) const override {
259 int number_of_grid_points = static_cast<int>(density.size());
260 xc_potential.resize(number_of_grid_points);
261 gradient_correction_density.resize(number_of_grid_points);
262 xc_energy_density.resize(number_of_grid_points);
263
264 constexpr Real rho_threshold_lda = 1e-10;
265 constexpr Real rho_threshold_gga = 1e-6;
266 constexpr Real gradient_threshold_gga = 1e-10;
267
268 ensure_work_buffers_size(number_of_grid_points);
269 auto& exchange_energies = work_exchange_energies_;
270 auto& correlation_energies = work_correlation_energies_;
271 auto& exchange_potentials = work_exchange_potentials_;
272 auto& correlation_potentials = work_correlation_potentials_;
273 auto& vsigma_exchange = work_vsigma_exchange_;
274 auto& vsigma_correlation = work_vsigma_correlation_;
275 auto& absolute_density = work_absolute_density_;
276 auto& density_gradient_squared_clamped = work_density_gradient_squared_clamped_;
277
278 auto* non_const_exchange = const_cast<xc_func_type*>(&func_x_);
279 auto* non_const_correlation = const_cast<xc_func_type*>(&func_c_);
280
281 for_each_grid_slice(number_of_grid_points, [&](int offset, int count) {
282 for (int grid_point_index = offset; grid_point_index < offset + count;
283 grid_point_index++) {
284 absolute_density[grid_point_index] = std::abs(density[grid_point_index]);
285 density_gradient_squared_clamped[grid_point_index] =
286 std::max(gradient_density_squared[grid_point_index], 0.0);
287 }
288
289 if (exchange_requires_gradient_) {
290 xc_gga_exc_vxc(non_const_exchange,
291 count,
292 absolute_density.data() + offset,
293 density_gradient_squared_clamped.data() + offset,
294 exchange_energies.data() + offset,
295 exchange_potentials.data() + offset,
296 vsigma_exchange.data() + offset);
297 } else {
298 xc_lda_exc_vxc(non_const_exchange,
299 count,
300 absolute_density.data() + offset,
301 exchange_energies.data() + offset,
302 exchange_potentials.data() + offset);
303 std::fill_n(vsigma_exchange.begin() + offset, count, 0.0);
304 }
305
306 if (correlation_requires_gradient_) {
307 xc_gga_exc_vxc(non_const_correlation,
308 count,
309 absolute_density.data() + offset,
310 density_gradient_squared_clamped.data() + offset,
311 correlation_energies.data() + offset,
312 correlation_potentials.data() + offset,
313 vsigma_correlation.data() + offset);
314 } else {
315 xc_lda_exc_vxc(non_const_correlation,
316 count,
317 absolute_density.data() + offset,
318 correlation_energies.data() + offset,
319 correlation_potentials.data() + offset);
320 std::fill_n(vsigma_correlation.begin() + offset, count, 0.0);
321 }
322 });
323
324 for (int grid_point_index = 0; grid_point_index < number_of_grid_points;
325 grid_point_index++) {
326 if (absolute_density[grid_point_index] <= rho_threshold_lda) {
327 xc_energy_density[grid_point_index] = 0.0;
328 xc_potential[grid_point_index] = 0.0;
329 gradient_correction_density[grid_point_index] = 0.0;
330 continue;
331 }
332
333 xc_energy_density[grid_point_index] =
335 (exchange_energies[grid_point_index] + correlation_energies[grid_point_index]);
336 xc_potential[grid_point_index] =
338 (exchange_potentials[grid_point_index] + correlation_potentials[grid_point_index]);
339
340 if (absolute_density[grid_point_index] <= rho_threshold_gga ||
341 std::abs(density_gradient_squared_clamped[grid_point_index]) <=
342 gradient_threshold_gga) {
343 gradient_correction_density[grid_point_index] = 0.0;
344 } else {
345 gradient_correction_density[grid_point_index] =
347 (vsigma_exchange[grid_point_index] + vsigma_correlation[grid_point_index]);
348 }
349 if (!all_finite({xc_energy_density[grid_point_index],
350 xc_potential[grid_point_index],
351 gradient_correction_density[grid_point_index]})) {
352 throw_non_finite_output(
353 "unpolarized GGA",
354 grid_point_index,
355 {{"rho", absolute_density[grid_point_index]},
356 {"sigma", density_gradient_squared_clamped[grid_point_index]}});
357 }
358 }
359 }
360
361 // ─── Spin-polarized GGA ─────────────────────────────────────────────
362 void
363 compute_gradient_corrected_spin(const std::vector<Real>& density_up,
364 const std::vector<Real>& density_down,
365 const std::vector<Real>& gradient_density_squared_up_up,
366 const std::vector<Real>& gradient_density_squared_up_down,
367 const std::vector<Real>& gradient_density_squared_down_down,
368 std::vector<Real>& xc_potential_up,
369 std::vector<Real>& xc_potential_down,
370 std::vector<Real>& gradient_correction_up_up,
371 std::vector<Real>& gradient_correction_up_down,
372 std::vector<Real>& gradient_correction_down_down,
373 std::vector<Real>& xc_energy_density) const override {
374 int total_grid_points = static_cast<int>(density_up.size());
375 xc_potential_up.resize(total_grid_points);
376 xc_potential_down.resize(total_grid_points);
377 gradient_correction_up_up.resize(total_grid_points);
378 gradient_correction_up_down.resize(total_grid_points);
379 gradient_correction_down_down.resize(total_grid_points);
380 xc_energy_density.resize(total_grid_points);
381
382 constexpr Real rho_threshold_lda = 1e-10;
383 constexpr Real rho_threshold_gga = 1e-6;
384 constexpr Real gradient_threshold_gga = 1e-10;
385
386 std::vector<Real> density_array(2 * total_grid_points);
387 std::vector<Real> gradient_squared_array(3 * total_grid_points);
388 for (int grid_point_index = 0; grid_point_index < total_grid_points; grid_point_index++) {
389 density_array[2 * grid_point_index] = std::abs(density_up[grid_point_index]);
390 density_array[2 * grid_point_index + 1] = std::abs(density_down[grid_point_index]);
391
392 gradient_squared_array[3 * grid_point_index] =
393 std::max(gradient_density_squared_up_up[grid_point_index], 0.0);
394 gradient_squared_array[3 * grid_point_index + 1] =
395 gradient_density_squared_up_down[grid_point_index];
396 gradient_squared_array[3 * grid_point_index + 2] =
397 std::max(gradient_density_squared_down_down[grid_point_index], 0.0);
398 }
399
400 std::vector<Real> exchange_energies(total_grid_points);
401 std::vector<Real> correlation_energies(total_grid_points);
402 std::vector<Real> exchange_density_potential(2 * total_grid_points, 0.0);
403 std::vector<Real> correlation_density_potential(2 * total_grid_points, 0.0);
404 std::vector<Real> exchange_gradient_potential(3 * total_grid_points, 0.0);
405 std::vector<Real> correlation_gradient_potential(3 * total_grid_points, 0.0);
406
407 auto* non_const_exchange = const_cast<xc_func_type*>(&func_x_);
408 auto* non_const_correlation = const_cast<xc_func_type*>(&func_c_);
409
410 if (exchange_requires_gradient_) {
411 xc_gga_exc_vxc(non_const_exchange,
412 total_grid_points,
413 density_array.data(),
414 gradient_squared_array.data(),
415 exchange_energies.data(),
416 exchange_density_potential.data(),
417 exchange_gradient_potential.data());
418 } else {
419 xc_lda_exc_vxc(non_const_exchange,
420 total_grid_points,
421 density_array.data(),
422 exchange_energies.data(),
423 exchange_density_potential.data());
424 }
425
426 if (correlation_requires_gradient_) {
427 xc_gga_exc_vxc(non_const_correlation,
428 total_grid_points,
429 density_array.data(),
430 gradient_squared_array.data(),
431 correlation_energies.data(),
432 correlation_density_potential.data(),
433 correlation_gradient_potential.data());
434 } else {
435 xc_lda_exc_vxc(non_const_correlation,
436 total_grid_points,
437 density_array.data(),
438 correlation_energies.data(),
439 correlation_density_potential.data());
440 }
441
442 for (int grid_point_index = 0; grid_point_index < total_grid_points; grid_point_index++) {
443 Real density_up = density_array[2 * grid_point_index];
444 Real density_down = density_array[2 * grid_point_index + 1];
445 Real gradient_squared_up = std::abs(gradient_squared_array[3 * grid_point_index]);
446 Real gradient_squared_down = std::abs(gradient_squared_array[3 * grid_point_index + 2]);
447
448 if (density_up <= rho_threshold_lda || density_down <= rho_threshold_lda) {
449 xc_energy_density[grid_point_index] = 0.0;
450 xc_potential_up[grid_point_index] = xc_potential_down[grid_point_index] = 0.0;
451 gradient_correction_up_up[grid_point_index] =
452 gradient_correction_up_down[grid_point_index] =
453 gradient_correction_down_down[grid_point_index] = 0.0;
454 continue;
455 }
456
457 xc_energy_density[grid_point_index] =
459 (exchange_energies[grid_point_index] + correlation_energies[grid_point_index]);
460 xc_potential_up[grid_point_index] =
462 (exchange_density_potential[2 * grid_point_index] +
463 correlation_density_potential[2 * grid_point_index]);
464 xc_potential_down[grid_point_index] =
466 (exchange_density_potential[2 * grid_point_index + 1] +
467 correlation_density_potential[2 * grid_point_index + 1]);
468
469 if (density_up <= rho_threshold_gga || density_down <= rho_threshold_gga ||
470 gradient_squared_up <= gradient_threshold_gga ||
471 gradient_squared_down <= gradient_threshold_gga) {
472 gradient_correction_up_up[grid_point_index] =
473 gradient_correction_up_down[grid_point_index] =
474 gradient_correction_down_down[grid_point_index] = 0.0;
475 } else {
476 gradient_correction_up_up[grid_point_index] =
478 (exchange_gradient_potential[3 * grid_point_index] +
479 correlation_gradient_potential[3 * grid_point_index]);
480 gradient_correction_up_down[grid_point_index] =
482 (exchange_gradient_potential[3 * grid_point_index + 1] +
483 correlation_gradient_potential[3 * grid_point_index + 1]);
484 gradient_correction_down_down[grid_point_index] =
486 (exchange_gradient_potential[3 * grid_point_index + 2] +
487 correlation_gradient_potential[3 * grid_point_index + 2]);
488 }
489
490 // Safety clamps
491 auto clamp_potential_value = [](Real& value, Real limit) {
492 if (std::abs(value) > limit) {
493 value = (value > 0) ? limit : -limit;
494 }
495 };
496 clamp_potential_value(xc_potential_up[grid_point_index], 100.0);
497 clamp_potential_value(xc_potential_down[grid_point_index], 100.0);
498 clamp_potential_value(gradient_correction_up_up[grid_point_index], 100.0);
499 clamp_potential_value(gradient_correction_up_down[grid_point_index], 100.0);
500 clamp_potential_value(gradient_correction_down_down[grid_point_index], 100.0);
501
502 if (!all_finite({xc_energy_density[grid_point_index],
503 xc_potential_up[grid_point_index],
504 xc_potential_down[grid_point_index],
505 gradient_correction_up_up[grid_point_index],
506 gradient_correction_up_down[grid_point_index],
507 gradient_correction_down_down[grid_point_index]})) {
508 throw_non_finite_output(
509 "spin-polarized GGA",
510 grid_point_index,
511 {{"rho_up", density_up},
512 {"rho_down", density_down},
513 {"sigma_up_up", gradient_squared_array[3 * grid_point_index]},
514 {"sigma_up_down", gradient_squared_array[3 * grid_point_index + 1]},
515 {"sigma_down_down", gradient_squared_array[3 * grid_point_index + 2]}});
516 }
517 }
518 }
519
520 private:
521 /**
522 * The shortest slice of grid worth handing to a thread.
523 *
524 * Forking a team costs a few microseconds; a LibXC evaluation costs tens
525 * of nanoseconds a point. Below roughly this many points the team is the
526 * more expensive half of the work, so `for_each_grid_slice` runs the whole
527 * grid inline instead.
528 */
529 static constexpr int minimum_grid_points_per_thread = 4096;
530
531 /**
532 * Run `evaluate_slice(offset, count)` over contiguous slices that together
533 * cover [0, number_of_grid_points), one slice per thread.
534 *
535 * Every functional evaluated through here is pointwise: a grid point's
536 * outputs depend on that point's inputs and on nothing else. Splitting the
537 * arrays therefore reproduces what one whole-grid call would have written,
538 * to the bit and not merely to a tolerance, whatever the slices are -- so
539 * these results do not move with the thread count. Nothing is summed
540 * across slices, so there is no summation order to preserve.
541 *
542 * LibXC's evaluators read the functional and write only the output arrays
543 * they are handed, which is what lets several threads drive one
544 * `xc_func_type` over disjoint slices.
545 *
546 * `evaluate_slice` must not throw: an exception that escapes a parallel
547 * region terminates the program. Callers do their finiteness checks in a
548 * serial pass afterwards.
549 */
550 template <typename SliceEvaluator>
551 static void for_each_grid_slice(int number_of_grid_points, SliceEvaluator evaluate_slice) {
552 [[maybe_unused]] int slice_threads = execution::threading::thread_count_for(
553 number_of_grid_points, minimum_grid_points_per_thread);
554#pragma omp parallel num_threads(slice_threads) if (slice_threads > 1)
555 {
556 long long first_point = 0;
557 long long last_point = number_of_grid_points;
558 execution::threading::slice_bounds(number_of_grid_points,
561 first_point,
562 last_point);
563 if (last_point > first_point) {
564 evaluate_slice(static_cast<int>(first_point),
565 static_cast<int>(last_point - first_point));
566 }
567 }
568 }
569
570 [[nodiscard]] static bool all_finite(std::initializer_list<Real> values) {
571 for (Real value : values) {
572 if (!std::isfinite(value)) {
573 return false;
574 }
575 }
576 return true;
577 }
578
579 /**
580 * Refuse a value LibXC returned non-finite, at a point Q3 is about to use.
581 *
582 * Every caller checks the values it is about to store, after its density
583 * floor, GGA screening and safety clamps, so a point Q3 zeroes or bounds
584 * cannot trip this; what reaches it goes into the potential or the energy.
585 * Without it a NaN travelled silently -- the clamps compare magnitudes,
586 * which a NaN never exceeds -- and surfaced a step later as a failed
587 * subspace eigensolve and NaN energies. LibXC 7.1.0 to 7.1.2 do exactly
588 * that for spin-polarized PBE correlation wherever the total density
589 * gradient vanishes while the spin gradients do not, as between the
590 * opposite moments of an antiferromagnet. The build takes a system LibXC
591 * only from the series Q3 is tested against, so this is what stops an
592 * untested one when it is forced, and only then does the message point at
593 * the version. It is formatted only here, so the per-point cost is one
594 * isfinite per value.
595 */
596 [[noreturn]] void
597 throw_non_finite_output(const char* evaluation,
598 int grid_point_index,
599 std::initializer_list<std::pair<const char*, Real>> inputs) const {
600 std::string input_description;
601 for (const auto& [name, value] : inputs) {
602 char buffer[64];
603 std::snprintf(buffer,
604 sizeof(buffer),
605 "%s%s=%.17g",
606 input_description.empty() ? "" : ", ",
607 name,
608 value);
609 input_description += buffer;
610 }
611 std::string message = std::string("LibXC ") + xc_version_string() +
612 " returned a non-finite value in " + evaluation + " evaluation of '" +
613 functional_name_ + "' at grid point " +
614 std::to_string(grid_point_index) + " (" + input_description + ").";
615
616 // The series Q3_TESTED_LIBXC_SERIES in CMakeLists.txt accepts from the system.
617 int major_version = 0;
618 int minor_version = 0;
619 int micro_version = 0;
620 xc_version(&major_version, &minor_version, &micro_version);
621 const bool tested_series = (major_version == 5 && minor_version == 2) ||
622 (major_version == 7 && minor_version == 0);
623 if (!tested_series) {
624 message += " Q3 is tested against LibXC 5.2 and 7.0; 7.1.0 to 7.1.2 return NaN for "
625 "the gradient derivatives of spin-polarized PBE correlation where the "
626 "total density gradient vanishes. Without Q3_ALLOW_UNTESTED_SYSTEM_LIBXC "
627 "the build uses a tested LibXC, building 7.0.0 from source if it has "
628 "to; see KNOWN-ISSUES.md.";
629 }
630 throw std::runtime_error(message);
631 }
632
633 [[nodiscard]] static std::string family_name(int family) {
634 switch (family) {
635 case XC_FAMILY_LDA:
636 return "LDA";
637 case XC_FAMILY_GGA:
638 return "GGA";
639 case XC_FAMILY_MGGA:
640 return "MGGA";
641 case XC_FAMILY_LCA:
642 return "LCA";
643 case XC_FAMILY_OEP:
644 return "OEP";
645 case XC_FAMILY_HYB_GGA:
646 return "HYB_GGA";
647 case XC_FAMILY_HYB_MGGA:
648 return "HYB_MGGA";
649 case XC_FAMILY_HYB_LDA:
650 return "HYB_LDA";
651 default:
652 return "UNKNOWN(" + std::to_string(family) + ")";
653 }
654 }
655
656 [[nodiscard]] static std::string missing_capability(int family) {
657 switch (family) {
658 case XC_FAMILY_MGGA:
659 return "kinetic-energy-density and density-Laplacian inputs";
660 case XC_FAMILY_HYB_LDA:
661 case XC_FAMILY_HYB_GGA:
662 return "nonlocal exact-exchange operator";
663 case XC_FAMILY_HYB_MGGA:
664 return "nonlocal exact-exchange operator and kinetic-energy-density inputs";
665 default:
666 return "family-specific evaluation path";
667 }
668 }
669
670 static void ensure_supported_family(const std::string& functional_identifier,
671 const std::string& component_name,
672 int functional_identifier_number,
673 const xc_func_type& functional,
674 int family) {
675 if (family == XC_FAMILY_LDA || family == XC_FAMILY_GGA) {
676 return;
677 }
678
679 throw std::runtime_error(
680 "Unsupported LibXC functional identifier '" + functional_identifier + "': " +
681 component_name + " component id=" + std::to_string(functional_identifier_number) +
682 " ('" + std::string(functional.info->name) + "') has family " + family_name(family) +
683 "; Q3 does not implement the required " + missing_capability(family) +
684 ". Q3 currently supports only complete LDA and GGA LibXC evaluation.");
685 }
686
687 xc_func_type func_x_;
688 xc_func_type func_c_;
689 int exchange_id_;
690 int correlation_id_;
691 bool exchange_requires_gradient_;
692 bool correlation_requires_gradient_;
693 int number_of_spin_components_;
694 std::string functional_name_;
695
696 /**
697 * Cached work buffers to avoid per-call heap allocation in GGA methods.
698 * These are mutable because compute methods are const.
699 */
700 mutable std::vector<Real> work_exchange_energies_;
701 mutable std::vector<Real> work_correlation_energies_;
702 mutable std::vector<Real> work_exchange_potentials_;
703 mutable std::vector<Real> work_correlation_potentials_;
704 mutable std::vector<Real> work_vsigma_exchange_;
705 mutable std::vector<Real> work_vsigma_correlation_;
706 mutable std::vector<Real> work_absolute_density_;
707 mutable std::vector<Real> work_density_gradient_squared_clamped_;
708
709 /**
710 * Ensure all work buffers are at least number_of_grid_points in size.
711 * Only reallocates if the current size is insufficient.
712 */
713 void ensure_work_buffers_size(int number_of_grid_points) const {
714 auto ensure = [](std::vector<Real>& buffer, int size) {
715 if (static_cast<int>(buffer.size()) < size) {
716 buffer.resize(size);
717 }
718 };
719 ensure(work_exchange_energies_, number_of_grid_points);
720 ensure(work_correlation_energies_, number_of_grid_points);
721 ensure(work_exchange_potentials_, number_of_grid_points);
722 ensure(work_correlation_potentials_, number_of_grid_points);
723 ensure(work_vsigma_exchange_, number_of_grid_points);
724 ensure(work_vsigma_correlation_, number_of_grid_points);
725 ensure(work_absolute_density_, number_of_grid_points);
726 ensure(work_density_gradient_squared_clamped_, number_of_grid_points);
727 }
728};
729
730} // namespace q3
Abstract base class for XC functionals.
Definition init.hpp:23
void compute_gradient_corrected_spin(const std::vector< Real > &density_up, const std::vector< Real > &density_down, const std::vector< Real > &gradient_density_squared_up_up, const std::vector< Real > &gradient_density_squared_up_down, const std::vector< Real > &gradient_density_squared_down_down, std::vector< Real > &xc_potential_up, std::vector< Real > &xc_potential_down, std::vector< Real > &gradient_correction_up_up, std::vector< Real > &gradient_correction_up_down, std::vector< Real > &gradient_correction_down_down, std::vector< Real > &xc_energy_density) const override
Definition libxc.hpp:363
int number_of_spin_components() const override
Definition libxc.hpp:109
void compute_gradient_corrected(const std::vector< Real > &density, const std::vector< Real > &gradient_density_squared, std::vector< Real > &xc_potential, std::vector< Real > &gradient_correction_density, std::vector< Real > &xc_energy_density) const override
Definition libxc.hpp:254
std::string name() const override
Definition libxc.hpp:113
void compute_spin(const std::vector< Real > &density_up, const std::vector< Real > &density_down, std::vector< Real > &xc_potential_up, std::vector< Real > &xc_potential_down, std::vector< Real > &xc_energy_density) const override
Definition libxc.hpp:182
~XC_LibXC() override
Definition libxc.hpp:100
bool requires_density_gradient() const override
Whether this is a GGA functional (needs gradients).
Definition libxc.hpp:105
XC_LibXC(int exchange_id, int correlation_id, int number_of_spin_components=1, std::string functional_identifier="")
Definition libxc.hpp:42
void compute(const std::vector< Real > &density, std::vector< Real > &xc_potential, std::vector< Real > &xc_energy_density) const override
Compute XC potential and energy density (LDA interface, unpolarized).
Definition libxc.hpp:118
constexpr Real electron_charge_squared_rydberg
Definition constants.hpp:32
int thread_count_for(long long work_items, long long minimum_items_per_thread)
void slice_bounds(long long work_items, int number_of_slices, int slice_index, long long &first, long long &last)
constexpr double minimum_electron_density
double Real