Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
charge_atomic.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Atomic charge density tabulation free function.
5 *
6 * Pure function that computes atomic charge density in G-space via
7 * radial Bessel transform on a uniform q-grid, then Lagrange interpolation
8 * to the actual G-vector magnitudes.
9 */
10
11#include <cmath>
12#include <stdexcept>
13#include <vector>
14
23
24namespace q3 {
25
26/**
27 * Tabulate atomic charge density in G-space for all species.
28 *
29 * Uses Simpson's rule on a uniform q-grid, then 4-point Lagrange interpolation
30 * to evaluate at each G-vector magnitude.
31 *
32 * @return atomic_charge_density_g[species_type][g_index]
33 */
34inline std::vector<std::vector<Real>>
36 const std::vector<int>& mesh_sizes,
37 const std::vector<Real>& g_vector_magnitudes,
38 Real q_spacing,
39 int number_of_q_points) {
40
41 int number_of_species_types = static_cast<int>(pseudopotential_set.size());
42 int number_of_g_vectors = static_cast<int>(g_vector_magnitudes.size());
43
44 // Build q-grid for tabulation
45 std::vector<Real> q_point_values(number_of_q_points);
46 for (int q_index = 0; q_index < number_of_q_points; q_index++) {
47 q_point_values[q_index] = q_index * q_spacing;
48 }
49
50 // Tabulate on q-grid via Simpson's rule
51 std::vector<std::vector<Real>> atomic_charge_density_table(number_of_species_types);
52 for (int type_index = 0; type_index < number_of_species_types; type_index++) {
53 /**
54 * Some datasets state this quantity in reciprocal space only, so they
55 * arrive with nothing radial to transform, and their shipped table is
56 * resampled onto Q3's q-grid instead. Same situation as the
57 * projectors, same resolution: the conventions already agree (both
58 * give the valence charge at q = 0), so this is a change of grid and
59 * nothing else.
60 *
61 * Reconstructing ρ_at from the partial waves is not an alternative: a
62 * dataset's reference occupancies need not be the neutral atom's.
63 */
64 const auto* paw = dynamic_cast<const PawPseudopotential*>(&pseudopotential_set[type_index]);
65 if (paw != nullptr && paw->has_reciprocal_atomic_charge()) {
66 resample_uniform_reciprocal_table(paw->charge_atomic_reciprocal,
67 paw->reciprocal_charge_grid_spacing,
68 0,
69 q_point_values,
70 atomic_charge_density_table[type_index]);
71 continue;
72 }
73 if (pseudopotential_set[type_index].charge_atomic.radial_values.empty()) {
74 throw std::runtime_error(
75 "Pseudopotential for " + pseudopotential_set[type_index].element +
76 " carries no atomic charge density in either representation, so no starting "
77 "density can be built from it.");
78 }
79 const auto& pseudopotential = pseudopotential_set[type_index];
80 int radial_mesh_size = mesh_sizes[type_index];
81 atomic_charge_density_table[type_index].resize(number_of_q_points);
82 for (int q_index = 0; q_index < number_of_q_points; q_index++) {
83 Real q_value = q_point_values[q_index];
84 Real sum = 0.0;
85 for (int radial_index = 0; radial_index < radial_mesh_size; radial_index++) {
86 Real bessel_value =
87 spherical_bessel(0, q_value * pseudopotential.radial_grid[radial_index]);
88 Real integrand = pseudopotential.charge_atomic.radial_values[radial_index] *
89 bessel_value *
90 pseudopotential.radial_grid_derivative[radial_index];
91 Real weight;
92 if (radial_index == 0 || radial_index == radial_mesh_size - 1)
93 weight = 1.0;
94 else if (radial_index % 2 == 1)
95 weight = 4.0;
96 else
97 weight = 2.0;
98 sum += weight * integrand;
99 }
100 atomic_charge_density_table[type_index][q_index] = sum / 3.0;
101 }
102 }
103
104 // Interpolate to G-vectors using 4-point Lagrange
105 std::vector<std::vector<Real>> atomic_charge_density_g(number_of_species_types);
106 for (int type_index = 0; type_index < number_of_species_types; type_index++) {
107 atomic_charge_density_g[type_index].resize(number_of_g_vectors);
108 for (int g_vector_index = 0; g_vector_index < number_of_g_vectors; g_vector_index++) {
109 Real q = g_vector_magnitudes[g_vector_index];
111 atomic_charge_density_g[type_index][g_vector_index] =
112 atomic_charge_density_table[type_index][0];
113 } else {
114 atomic_charge_density_g[type_index][g_vector_index] = lagrange_interpolation_4point(
115 atomic_charge_density_table[type_index], q, q_spacing, number_of_q_points);
116 }
117 if (std::isnan(atomic_charge_density_g[type_index][g_vector_index])) {
118 log::error("Atomic charge density G-space coefficient is NaN: "
119 "species_type={}, g_index={}, q_value={}",
120 type_index,
121 g_vector_index,
122 q);
123 }
124 }
125 }
126
127 return atomic_charge_density_g;
128}
129
130} // namespace q3
PAW pseudopotential — extends ultrasoft with one-center (on-site) data.
Definition init.hpp:33
bool empty() const
Definition set.hpp:102
int size() const
Definition set.hpp:98
void error(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:192
constexpr double zero_vector_magnitude_threshold
std::vector< std::vector< Real > > tabulate_atomic_charge_density_in_g_space(const PseudopotentialSet &pseudopotential_set, const std::vector< int > &mesh_sizes, const std::vector< Real > &g_vector_magnitudes, Real q_spacing, int number_of_q_points)
double Real
void resample_uniform_reciprocal_table(const std::vector< Real > &samples, Real spacing, int angular_momentum, const std::vector< Real > &q_values, std::vector< Real > &result)
Real lagrange_interpolation_4point(const std::vector< Real > &table, Real q_value, Real q_spacing, int table_size)
Definition lagrange.hpp:31
Real spherical_bessel(int degree, Real argument)
Definition bessel.hpp:30