#include <broyden.hpp>
Inheritance diagram for q3::BroydenMixer:Public Member Functions | |
| BroydenMixer (Real mixing_parameter=0.7, int history_depth=8) | |
| 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 |
| std::string | name () const override |
| void | reset () override |
Public Member Functions inherited from q3::ReciprocalSpaceMixer | |
| void | initialize (const MixerEnvironment &environment) override |
| Supply the representation context the mixer needs before the first mix(). | |
| void | mix (const std::vector< std::vector< Real > > &input_density, std::vector< std::vector< Real > > &output_density, int number_of_spin_components) override |
| Mixer (Real mixing_parameter, int history_depth) | |
Public Member Functions inherited from q3::Mixer | |
| Mixer (Real mixing_parameter, int history_depth) | |
| virtual | ~Mixer ()=default |
| int | history_depth () const |
| Real | last_residual_norm () const |
| Real | mixing_parameter () const |
Additional Inherited Members | |
Static Public Member Functions inherited from q3::ReciprocalSpaceMixer | |
| static void | convert_total_magnetization_to_up_down (std::vector< std::vector< Complex > > &density_g) |
| In-place (ρ↑+ρ↓, ρ↑−ρ↓) → (ρ↑, ρ↓). | |
| static void | convert_up_down_to_total_magnetization (std::vector< std::vector< Complex > > &density_g) |
| In-place (ρ↑, ρ↓) → (ρ↑+ρ↓, ρ↑−ρ↓). | |
| static std::vector< Real > | sum_over_spin_channels (const std::vector< std::vector< Real > > &density_real, int number_of_spin_components) |
| Total real-space density ρ↑+ρ↓ (a copy of channel 0 when unpolarized). | |
Protected Member Functions inherited from q3::ReciprocalSpaceMixer | |
| 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 |
Protected Attributes inherited from q3::ReciprocalSpaceMixer | |
| std::vector< Real > | charge_metric_weights_ |
| int | first_nonzero_g_index_ {0} |
| bool | is_gamma_only_ {false} |
| Real | magnetization_metric_weight_ {0.0} |
| int | number_of_smooth_g_vectors_ {0} |
| std::shared_ptr< Preconditioner > | preconditioner_ |
| std::function< void(const std::vector< Complex > &, std::vector< Real > &)> | to_real_space_ |
| std::function< void(const std::vector< Real > &, std::vector< Complex > &)> | to_reciprocal_space_ |
Protected Attributes inherited from q3::Mixer | |
| int | history_depth_ |
| Real | last_residual_norm_ {-1.0} |
| Real | mixing_parameter_ |
Modified Broyden mixing in G-space with Hartree-metric inner product.
Implements D.D. Johnson's modified Broyden algorithm (PRB 38, 12807, 1988). Preconditioning (e.g. Kerker/Thomas-Fermi, PRB 23, 3082, 1981) is applied to the residuals BEFORE they enter the Broyden history, so the Gram matrix is built in preconditioned space. That placement is this implementation's own choice: it keeps long-wavelength charge-sloshing modes out of the Broyden subspace itself, not merely out of the final search direction.
Metric weights are supplied through initialize() before the first mix() call.
Definition at line 39 of file broyden.hpp.
|
inlineexplicit |
Definition at line 41 of file broyden.hpp.
|
inlineoverridevirtual |
Perform one mixing step in G-space.
One step of the Johnson modified-Broyden update, in the order applied:
Step 2 ahead of step 3 is what makes Kerker filter long-wavelength modes out of the Broyden subspace, not just out of the final search direction: the stored history lives entirely in preconditioned space. The pairs are stored raw, with no normalization – see the storage site below.
Determine the boundary between smooth and hard G-vectors. Broyden operates only on [0:smooth_g_vector_count], hard G-vectors get linear mixing.
Residual norm ⟨F|F⟩ in the shared two-channel metric, restricted to the smooth G-vector set – the range the Broyden history spans. This value is mixer-local: it drives the restart heuristic below, NOT the SCF convergence test, which computes its own residual (see PlanewaveKohnShamSCFIterator::compute_local_residual_between). The two use the same metric and the same G range, so they are comparable.
The preconditioned residual F_prec enters the Broyden history, so the Gram matrix is built in preconditioned space. This prevents long-wavelength charge-sloshing modes from corrupting the subspace. When preconditioning is disabled, F_prec = F (identity transform).
Thomas-Fermi preconditioning (Kerker, or local-TF) screens the Coulomb response of the TOTAL charge channel only. The magnetization channel (spin >= 1 in the (total, m) representation) has no Coulomb kernel — damping its long-wavelength components would freeze slow moment relaxation. The preconditioner is the Thomas-Fermi screening of the Coulomb kernel, and the magnetization has no Coulomb kernel to screen.
Delta vectors are stored WITHOUT normalization: raw (un-normalized) ΔF/Δρ vectors, with DSYTRF Bunch-Kaufman pivoting left to handle near-singularity. Normalization was removed because it caused gamma = ⟨ΔF̂|F⟩ to be unbounded: when convergence is slow (||F|| >> ||ΔF||), gamma >> 1, leading to oscillatory divergence of the density. Without normalization, gamma = ⟨ΔF|F⟩/||ΔF||² ≤ ||F||/||ΔF||, naturally bounded.
Precompute the metric-weighted twin of ΔF once, when it enters history, instead of re-applying the metric weight on every pairwise access below. See build_metric_scaled_copy() for why this is safe (see TODOs.md §2.1 "Accelerate Broyden G-space operations").
Save current preconditioned residual and input for next iteration's delta computation. A secant pair has to relate a residual to the input that produced it, so both are snapshotted here, before the Broyden correction below overwrites rho_in_g in place.
Build Gram matrix β_{ij} = ⟨ΔF_i|ΔF_j⟩_metric (preconditioned delta residuals). One side of each pairwise dot product uses the precomputed metric-scaled history entry (metric_scaled_delta_residuals_), so the metric weight is applied once per history entry (O(history_depth)) rather than once per pair (O(history_depth^2)) – see build_metric_scaled_copy() below. The actual reduction is a plain, branch-free complex dot product (q3_complex_conjugate_dot_product, BLAS Level-1 style).
No diagonal regularization: the Gram matrix is inverted as built, with no w0² added. Two mechanisms cover the near-singular case instead – DSYTRF's Bunch-Kaufman pivoting, and the fall-back to linear mixing on a non-zero LAPACK info below. Adding w0² was incorrect and interacted badly with the normalization that has also been removed.
Invert β via the LAPACK symmetric-indefinite path: DSYTRF factorizes with Bunch-Kaufman pivoting, DSYTRI inverts from that factorization (DSYTRI's contract is to consume DSYTRF's output). Not Cholesky: β is only positive SEMI-definite once the stored ΔF go nearly linearly dependent.
Compute projection γ_i = Σ_j β⁻¹_{ij} · ⟨ΔF_j | F_prec⟩_metric The projection uses the preconditioned current residual, because that is the space the history is stored in – both sides of ⟨ΔF_j|F⟩ must live in the same space for β⁻¹ to be the right operator to apply. Reuses the same metric_scaled_delta_residuals_ entries built above – no additional metric-weight application needed here.
Apply the Broyden update over the smooth G-vectors only. On a dual grid the hard G-vectors carry the augmentation charge; they stay outside the quasi-Newton subspace and are linear-mixed by the caller (ReciprocalSpaceMixer::mix) instead: ρ_in -= Σ γ_i · Δρ_i (density correction in density space) F_prec -= Σ γ_i · ΔF_i (preconditioned residual correction) Each inner update is a scaled vector addition (y += alpha*x), so it maps directly onto BLAS ZAXPY rather than a hand-rolled per-element loop.
Final mixing step: ρ_new = ρ_in + α · F_prec (smooth G-vectors only). Hard G-vectors are outside the Broyden subspace and are handled exclusively by the outer ReciprocalSpaceMixer::mix() via beta-weighted linear mixing. Limiting this call to smooth_g_vector_count prevents double-application of linear mixing to hard G.
Implements q3::ReciprocalSpaceMixer.
Definition at line 64 of file broyden.hpp.
References q3::ReciprocalSpaceMixer::compute_metric_dot_product(), q3::log::debug(), q3::lapack::dsytrf_(), q3::lapack::dsytri_(), q3::Mixer::history_depth_, q3::log::info(), q3::Mixer::last_residual_norm_, q3::ReciprocalSpaceMixer::number_of_smooth_g_vectors_, q3::ReciprocalSpaceMixer::preconditioner_, q3::log::warn(), and q3::blas::zaxpy_().
|
inlineoverridevirtual |
Implements q3::Mixer.
Definition at line 401 of file broyden.hpp.
|
inlineoverridevirtual |
Reimplemented from q3::Mixer.
Definition at line 391 of file broyden.hpp.