Loading...
Searching...
No Matches
linear.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * LinearMixer — simple linear (plain) charge density mixer.
5 *
6 * Implements the simplest possible mixing scheme:
7 * ρ_new = ρ_in + α · (ρ_out - ρ_in)
8 *
9 * Slower per-iteration than Broyden, but unconditionally stable for small
10 * enough alpha, and it converges reliably for metallic systems where Broyden
11 * may diverge due to charge sloshing.
12 *
13 * Kerker preconditioning (Thomas-Fermi screening) can be enabled by
14 * supplying preconditioning_weights through the MixerEnvironment. When
15 * enabled, the search direction is damped at small G:
16 * ρ_new = ρ_in + α · K(G) · (ρ_out - ρ_in), K(G) = G²/(G²+q0²)
17 */
18
19#include <string>
20#include <vector>
21
23
24namespace q3 {
25
26/**
27 * Simple linear density mixer operating in G-space with optional Kerker preconditioning.
28 *
29 * Convergence is guaranteed for sufficiently small alpha, unlike Broyden which
30 * can diverge for difficult metallic systems.
31 *
32 * Not to be confused with mixing_mode='plain' in a Quantum ESPRESSO input:
33 * there, 'plain' only turns the preconditioner off and the Broyden history
34 * still runs, which is why io/input/parser/qe.hpp maps it to this code's
35 * Broyden mixer rather than to this class.
36 */
38 public:
40
41 /**
42 * Perform one linear mixing step in G-space.
43 *
44 * ρ_new = ρ_in + α · K(G) · (ρ_out - ρ_in)
45 *
46 * where K is the configured residual preconditioner (Kerker or local-TF if
47 * enabled, the identity otherwise).
48 */
49 void mix_in_reciprocal_space(std::vector<std::vector<Complex>>& rho_in_g,
50 const std::vector<std::vector<Complex>>& rho_out_g,
51 int number_of_spin_components) override {
52 int number_of_spin = static_cast<int>(rho_in_g.size());
53 int number_of_g_vectors = static_cast<int>(rho_in_g[0].size());
54 int smooth_g_vector_count =
55 (number_of_smooth_g_vectors_ > 0 && number_of_smooth_g_vectors_ < number_of_g_vectors)
57 : number_of_g_vectors;
58 /**
59 * Compute last_residual_norm_ BEFORE mixing using the pre-mix residual,
60 * in the same shared two-channel metric Broyden reports -- see
61 * ReciprocalSpaceMixer::compute_metric_dot_product().
62 */
63 std::vector<std::vector<Complex>> raw_residual(number_of_spin);
64 for (int spin_index = 0; spin_index < number_of_spin; spin_index++) {
65 raw_residual[spin_index].resize(smooth_g_vector_count);
66 for (int g_index = 0; g_index < smooth_g_vector_count; g_index++) {
67 raw_residual[spin_index][g_index] =
68 rho_out_g[spin_index][g_index] - rho_in_g[spin_index][g_index];
69 }
70 }
72 raw_residual, raw_residual, number_of_spin_components, smooth_g_vector_count);
73
74 /**
75 * Apply linear mixing: ρ_new = ρ_in + α · K(G) · (ρ_out - ρ_in)
76 * K(G) = G²/(G²+q0²) for Kerker, 1 otherwise.
77 * Only mix smooth G here. Hard G above smooth_g_vector_count are mixed
78 * separately, by the hard-G loop in ReciprocalSpaceMixer::mix().
79 */
80 for (int spin_index = 0; spin_index < number_of_spin; spin_index++) {
81 /**
82 * Charge channel: G=0 is fixed by charge conservation, and the
83 * preconditioner screens the Coulomb response. Magnetization channel
84 * (spin 1 in the (total, m) representation): the moment is not
85 * conserved, so its G=0 component must be mixed, and no screening
86 * applies (no Coulomb kernel for m).
87 */
88 bool is_charge_channel = (spin_index == 0);
89 int start_g_index = is_charge_channel ? first_nonzero_g_index_ : 0;
90
91 /**
92 * The preconditioner is an operator, so the residual is formed first
93 * and preconditioned as a whole rather than scaled per G on the fly.
94 * It is built over the full G range even though only the smooth
95 * range is mixed here -- a non-diagonal preconditioner couples G
96 * components, and truncating its input to the smooth range would
97 * solve a different problem. For a diagonal preconditioner this is
98 * exactly the previous per-G multiply.
99 */
100 std::vector<Complex> residual(rho_in_g[spin_index].size());
101 for (std::size_t g_index = 0; g_index < residual.size(); ++g_index) {
102 residual[g_index] = rho_out_g[spin_index][g_index] - rho_in_g[spin_index][g_index];
103 }
104 if (is_charge_channel && preconditioner_ != nullptr) {
105 preconditioner_->apply(residual);
106 }
107
108 for (int g_index = start_g_index; g_index < smooth_g_vector_count; g_index++) {
109 rho_in_g[spin_index][g_index] += mixing_parameter_ * residual[g_index];
110 }
111 }
112 }
113
114 [[nodiscard]] std::string name() const override {
115 return "linear";
116 }
117};
118
119} // namespace q3
LinearMixer(Real mixing_parameter=0.4)
Definition linear.hpp:39
void mix_in_reciprocal_space(std::vector< std::vector< Complex > > &rho_in_g, const std::vector< std::vector< Complex > > &rho_out_g, int number_of_spin_components) override
Definition linear.hpp:49
std::string name() const override
Definition linear.hpp:114
Real mixing_parameter_
Definition init.hpp:92
Real last_residual_norm_
Definition init.hpp:94
Real mixing_parameter() const
Definition init.hpp:73
std::shared_ptr< Preconditioner > preconditioner_
Definition init.hpp:206
Real compute_metric_dot_product(const std::vector< std::vector< Complex > > &rho_a, const std::vector< std::vector< Complex > > &rho_b, int number_of_spin_components, int g_vector_count=-1) const
Definition init.hpp:168
double Real