Loading...
Searching...
No Matches
local.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Local pseudopotential force contribution.
5 *
6 * Computes the Hellmann-Feynman force from the local (ionic) pseudopotential
7 * acting on the electron charge density in reciprocal space.
8 *
9 * The algorithm is the same for norm-conserving and ultrasoft pseudopotential_set.
10 */
11
12#include <cmath>
13#include <complex>
14#include <vector>
15
21
22namespace q3 {
23
25 public:
27 const PlanewaveBasis& planewave_basis,
28 const NormConservingPlanewaveBasis& pseudopotential_basis)
29 : crystal_structure_(crystal_structure)
30 , planewave_basis_(planewave_basis)
31 , pseudopotential_basis_(pseudopotential_basis) {}
32
33 std::vector<Vector3D> compute(const std::vector<Complex>& charge_density_g) {
34 std::vector<Vector3D> forces(crystal_structure_.number_of_atoms(), {0.0, 0.0, 0.0});
35 int first_nonzero_g_index = planewave_basis_.first_nonzero_g_index();
36 /**
37 * Gamma-only stores only the positive half sphere, and this
38 * summand is even under G -> -G (the imaginary part is odd and
39 * G_alpha is odd), so the omitted half contributes as much again.
40 * Verified against QE's own force decomposition at Gamma
41 * (`verbosity='high'`), where Q3 was exactly half.
42 */
43 Real half_sphere_scale = planewave_basis_.is_gamma_only() ? 2.0 : 1.0;
44
45 for (int atom_index = 0; atom_index < crystal_structure_.number_of_atoms(); atom_index++) {
46 int species_index = crystal_structure_.species_type_index(atom_index);
47 Vector3D atomic_position =
48 crystal_structure_.convert_from_crystal_to_cartesian(atom_index);
49
50 for (int g_vector_index = first_nonzero_g_index;
51 g_vector_index < planewave_basis_.number_of_g_vectors();
52 g_vector_index++) {
53 const auto& g_vector = planewave_basis_.g_vectors()[g_vector_index];
54 Real phase = constants::two_pi * g_vector.g_cart.dot(atomic_position);
55 /**
56 * F_{α,na} = Ω · G_α · Im[ V_loc(G) · ρ(G) · e^{+iGτ} ] (full sphere)
57 * Full-sphere sum over both G and -G: the -G term contributes the same as G,
58 * so no extra factor of 2 needed (it's already implicit in summing all G).
59 */
60 Complex vloc_rho =
61 pseudopotential_basis_.local_potential_g(species_index)[g_vector_index] *
62 charge_density_g[g_vector_index];
63 Complex exp_i_phase(std::cos(phase), std::sin(phase)); // e^{+iGτ}
64
65 for (int direction = 0; direction < 3; direction++) {
66 Real g_cartesian_component =
67 g_vector.g_cart[direction] *
68 crystal_structure_.two_pi_over_lattice_parameter(); // G_α in a.u.
69 Complex contribution = vloc_rho * exp_i_phase;
70 forces[atom_index][direction] += half_sphere_scale *
71 crystal_structure_.cell_volume() *
72 g_cartesian_component * contribution.imag();
73 }
74 }
75 }
76
77 return forces;
78 }
79
80 private:
81 const Crystal& crystal_structure_;
82 const PlanewaveBasis& planewave_basis_;
83 const NormConservingPlanewaveBasis& pseudopotential_basis_;
84};
85
86} // namespace q3
Vector3D convert_from_crystal_to_cartesian(int atom_index) const
Definition crystal.hpp:225
Real cell_volume() const
Definition crystal.hpp:193
Real two_pi_over_lattice_parameter() const
Definition crystal.hpp:196
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
std::vector< Vector3D > compute(const std::vector< Complex > &charge_density_g)
Definition local.hpp:33
LocalPseudopotentialForceCalculator(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const NormConservingPlanewaveBasis &pseudopotential_basis)
Definition local.hpp:26
const std::vector< Real > & local_potential_g(int species_type_index) const
Definition init.hpp:165
int number_of_g_vectors() const
Definition init.hpp:145
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
int first_nonzero_g_index() const
Definition init.hpp:151
bool is_gamma_only() const
Definition init.hpp:157
constexpr Real two_pi
Definition constants.hpp:25
double Real
std::complex< double > Complex