Loading...
Searching...
No Matches
q3::BroydenMixer Class Reference

#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_
 

Detailed Description

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.

Constructor & Destructor Documentation

◆ BroydenMixer()

q3::BroydenMixer::BroydenMixer ( Real  mixing_parameter = 0.7,
int  history_depth = 8 
)
inlineexplicit

Definition at line 41 of file broyden.hpp.

Member Function Documentation

◆ mix_in_reciprocal_space()

void q3::BroydenMixer::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 
)
inlineoverridevirtual

Perform one mixing step in G-space.

One step of the Johnson modified-Broyden update, in the order applied:

  1. Compute raw residual F = ρ_out - ρ_in
  2. Apply Kerker preconditioning: F_prec = K(G) · F (if enabled)
  3. Compute delta vectors in preconditioned space: ΔF = F_prec_prev - F_prec (preconditioned residual delta) Δρ = ρ_in_prev - ρ_in (raw density delta)
  4. Precompute the metric-weighted twin of each ΔF (build_metric_scaled_copy)
  5. Build Gram matrix β_{ij} = ⟨ΔF_i|ΔF_j⟩_metric (no w0² regularization)
  6. Solve for γ_i: γ = β⁻¹ · ⟨ΔF_i|F_prec⟩_metric
  7. Correct: ρ_in -= Σ γ_i Δρ_i; F_prec -= Σ γ_i ΔF_i
  8. Mix: ρ_new = ρ_in + α · F_prec

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_().

◆ name()

std::string q3::BroydenMixer::name ( ) const
inlineoverridevirtual

Implements q3::Mixer.

Definition at line 401 of file broyden.hpp.

◆ reset()

void q3::BroydenMixer::reset ( )
inlineoverridevirtual

Reimplemented from q3::Mixer.

Definition at line 391 of file broyden.hpp.


The documentation for this class was generated from the following file: