Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
broyden.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * BroydenMixer — modified Broyden (type-2) density mixer in reciprocal space.
5 *
6 * Implements the modified Broyden mixing scheme for SCF convergence
7 * acceleration, operating in reciprocal space with metric preconditioning.
8 * Maintains a history of density residuals and uses quasi-Newton updates
9 * to improve convergence.
10 */
11
12#include <algorithm>
13#include <cmath>
14#include <string>
15#include <vector>
16
17#include "io/logger.hpp"
20
23
24namespace q3 {
25using namespace q3::lapack;
26
27/**
28 * Modified Broyden mixing in G-space with Hartree-metric inner product.
29 *
30 * Implements D.D. Johnson's modified Broyden algorithm (PRB 38, 12807, 1988).
31 * Preconditioning (e.g. Kerker/Thomas-Fermi, PRB 23, 3082, 1981) is applied to
32 * the residuals BEFORE they enter the Broyden history, so the Gram matrix is
33 * built in preconditioned space. That placement is this implementation's own
34 * choice: it keeps long-wavelength charge-sloshing modes out of the Broyden
35 * subspace itself, not merely out of the final search direction.
36 *
37 * Metric weights are supplied through initialize() before the first mix() call.
38 */
40 public:
43
44 /**
45 * Perform one mixing step in G-space.
46 *
47 * One step of the Johnson modified-Broyden update, in the order applied:
48 * 1. Compute raw residual F = ρ_out - ρ_in
49 * 2. Apply Kerker preconditioning: F_prec = K(G) · F (if enabled)
50 * 3. Compute delta vectors in preconditioned space:
51 * ΔF = F_prec_prev - F_prec (preconditioned residual delta)
52 * Δρ = ρ_in_prev - ρ_in (raw density delta)
53 * 4. Precompute the metric-weighted twin of each ΔF (build_metric_scaled_copy)
54 * 5. Build Gram matrix β_{ij} = ⟨ΔF_i|ΔF_j⟩_metric (no w0² regularization)
55 * 6. Solve for γ_i: γ = β⁻¹ · ⟨ΔF_i|F_prec⟩_metric
56 * 7. Correct: ρ_in -= Σ γ_i Δρ_i; F_prec -= Σ γ_i ΔF_i
57 * 8. Mix: ρ_new = ρ_in + α · F_prec
58 *
59 * Step 2 ahead of step 3 is what makes Kerker filter long-wavelength modes
60 * out of the Broyden subspace, not just out of the final search direction:
61 * the stored history lives entirely in preconditioned space. The pairs are
62 * stored raw, with no normalization -- see the storage site below.
63 */
64 void mix_in_reciprocal_space(std::vector<std::vector<Complex>>& rho_in_g,
65 const std::vector<std::vector<Complex>>& rho_out_g,
66 int number_of_spin_components) override {
67 iteration_counter_++;
68
69 int number_of_spin = static_cast<int>(rho_in_g.size());
70 int number_of_g_vectors = static_cast<int>(rho_in_g[0].size());
71
72 /**
73 * Determine the boundary between smooth and hard G-vectors.
74 * Broyden operates only on [0:smooth_g_vector_count], hard G-vectors get linear mixing.
75 */
76 int smooth_g_vector_count =
77 (number_of_smooth_g_vectors_ > 0 && number_of_smooth_g_vectors_ < number_of_g_vectors)
79 : number_of_g_vectors;
80
81 // Compute raw residual F = ρ_out - ρ_in (full G range for linear mixing of hard G)
82 MixingRecord raw_residual;
83 raw_residual.data.resize(number_of_spin);
84 for (int spin_index = 0; spin_index < number_of_spin; spin_index++) {
85 raw_residual.data[spin_index].resize(number_of_g_vectors);
86 for (int g_index = 0; g_index < number_of_g_vectors; g_index++) {
87 raw_residual.data[spin_index][g_index] =
88 rho_out_g[spin_index][g_index] - rho_in_g[spin_index][g_index];
89 }
90 }
91
92 /**
93 * Residual norm ⟨F|F⟩ in the shared two-channel metric, restricted to the
94 * smooth G-vector set -- the range the Broyden history spans. This value is
95 * mixer-local: it drives the restart heuristic below, NOT the SCF convergence
96 * test, which computes its own residual (see
97 * PlanewaveKohnShamSCFIterator::compute_local_residual_between). The two use
98 * the same metric and the same G range, so they are comparable.
99 */
100 Real residual_norm = compute_metric_dot_product(
101 raw_residual.data, raw_residual.data, number_of_spin, smooth_g_vector_count);
102 last_residual_norm_ = residual_norm;
103
104 // Track best residual for restart detection.
105 if (residual_norm < best_residual_norm_ || best_residual_norm_ < 0.0) {
106 best_residual_norm_ = residual_norm;
107 stagnation_counter_ = 0;
108 } else {
109 stagnation_counter_++;
110 }
111
112 // Growth: residual norm > 5× best means the Jacobian approximation is degrading.
113 bool growth_detected = best_residual_norm_ > 0.0 &&
114 residual_norm > 5.0 * best_residual_norm_ &&
115 iteration_counter_ > history_depth_;
116 // Stagnation: no improvement in 2×ndim iterations.
117 bool stagnation_detected =
118 stagnation_counter_ >= 2 * history_depth_ && iteration_counter_ > history_depth_;
119
120 if (growth_detected || stagnation_detected) {
121 log::info(" Broyden mixer: {} (dr2={:.2e}, best={:.2e}, stagnation={}), "
122 "restarting history",
123 growth_detected ? "residual growth" : "stagnation",
124 residual_norm,
125 best_residual_norm_,
126 stagnation_counter_);
127 delta_residuals_.clear();
128 delta_inputs_.clear();
129 metric_scaled_delta_residuals_.clear();
130 has_previous_ = false;
131 iteration_counter_ = 1;
132 best_residual_norm_ = residual_norm;
133 stagnation_counter_ = 0;
134 }
135
136 /**
137 * The preconditioned residual F_prec enters the Broyden history,
138 * so the Gram matrix is built in preconditioned space. This prevents
139 * long-wavelength charge-sloshing modes from corrupting the subspace.
140 * When preconditioning is disabled, F_prec = F (identity transform).
141 */
142 MixingRecord preconditioned_residual;
143 preconditioned_residual.data.resize(number_of_spin);
144 for (int spin_index = 0; spin_index < number_of_spin; spin_index++) {
145 /**
146 * Thomas-Fermi preconditioning (Kerker, or local-TF) screens the Coulomb response of
147 * the TOTAL charge channel only. The magnetization channel (spin >= 1 in
148 * the (total, m) representation) has no Coulomb kernel — damping its
149 * long-wavelength components would freeze slow moment relaxation. The
150 * preconditioner is the Thomas-Fermi screening of the Coulomb kernel,
151 * and the magnetization has no Coulomb kernel to screen.
152 */
153 bool apply_preconditioning = (spin_index == 0) && (preconditioner_ != nullptr);
154 preconditioned_residual.data[spin_index] = raw_residual.data[spin_index];
155 if (apply_preconditioning) {
156 preconditioner_->apply(preconditioned_residual.data[spin_index]);
157 }
158 }
159
160 Real preconditioned_residual_norm = compute_metric_dot_product(preconditioned_residual.data,
161 preconditioned_residual.data,
162 number_of_spin,
163 smooth_g_vector_count);
164
165 auto log_diagnostics = [&](int number_of_history_entries) {
166 log::debug(" Broyden diagnostics: raw_dr2={:.8e} preconditioned_dr2={:.8e} "
167 "history_entries={}",
168 residual_norm,
169 preconditioned_residual_norm,
170 number_of_history_entries);
171 };
172
173 int iterations_used = std::min(iteration_counter_ - 1, history_depth_);
174
175 if (iteration_counter_ > 1 && has_previous_) {
176 MixingRecord delta_residual_new, delta_input_new;
177 delta_residual_new.data.resize(number_of_spin);
178 delta_input_new.data.resize(number_of_spin);
179 for (int spin_index = 0; spin_index < number_of_spin; spin_index++) {
180 delta_residual_new.data[spin_index].resize(smooth_g_vector_count);
181 delta_input_new.data[spin_index].resize(smooth_g_vector_count);
182 for (int g_index = 0; g_index < smooth_g_vector_count; g_index++) {
183 // ΔF = F_prec_prev - F_prec_current (residual delta, preconditioned space)
184 delta_residual_new.data[spin_index][g_index] =
185 previous_preconditioned_residual_.data[spin_index][g_index] -
186 preconditioned_residual.data[spin_index][g_index];
187 // Δρ = ρ_in_prev - ρ_in_current (input-density delta, density space)
188 delta_input_new.data[spin_index][g_index] =
189 previous_input_.data[spin_index][g_index] - rho_in_g[spin_index][g_index];
190 }
191 }
192
193 /**
194 * Delta vectors are stored WITHOUT normalization: raw (un-normalized)
195 * ΔF/Δρ vectors, with DSYTRF Bunch-Kaufman pivoting left to
196 * handle near-singularity. Normalization was removed because it caused
197 * gamma = ⟨ΔF̂|F⟩ to be unbounded: when convergence is slow (||F|| >> ||ΔF||),
198 * gamma >> 1, leading to oscillatory divergence of the density.
199 * Without normalization, gamma = ⟨ΔF|F⟩/||ΔF||² ≤ ||F||/||ΔF||, naturally bounded.
200 */
201
202 /**
203 * Precompute the metric-weighted twin of ΔF once, when it enters history,
204 * instead of re-applying the metric weight on every pairwise access below.
205 * See build_metric_scaled_copy() for why this is safe (see TODOs.md §2.1
206 * "Accelerate Broyden G-space operations").
207 */
208 MixingRecord scaled_delta_residual_new =
209 build_metric_scaled_copy(delta_residual_new, number_of_spin, smooth_g_vector_count);
210
211 // Store in circular buffer
212 if (static_cast<int>(delta_residuals_.size()) < history_depth_) {
213 delta_residuals_.push_back(std::move(delta_residual_new));
214 delta_inputs_.push_back(std::move(delta_input_new));
215 metric_scaled_delta_residuals_.push_back(std::move(scaled_delta_residual_new));
216 } else {
217 int position = (iteration_counter_ - 2) % history_depth_;
218 delta_residuals_[position] = std::move(delta_residual_new);
219 delta_inputs_[position] = std::move(delta_input_new);
220 metric_scaled_delta_residuals_[position] = std::move(scaled_delta_residual_new);
221 }
222 }
223
224 /**
225 * Save current preconditioned residual and input for next iteration's delta computation.
226 * A secant pair has to relate a residual to the input that produced it, so both are
227 * snapshotted here, before the Broyden correction below overwrites rho_in_g in place.
228 */
229 previous_preconditioned_residual_.data.resize(number_of_spin);
230 previous_input_.data.resize(number_of_spin);
231 for (int spin_index = 0; spin_index < number_of_spin; spin_index++) {
232 previous_preconditioned_residual_.data[spin_index].resize(smooth_g_vector_count);
233 previous_input_.data[spin_index].resize(smooth_g_vector_count);
234 for (int g_index = 0; g_index < smooth_g_vector_count; g_index++) {
235 previous_preconditioned_residual_.data[spin_index][g_index] =
236 preconditioned_residual.data[spin_index][g_index];
237 previous_input_.data[spin_index][g_index] = rho_in_g[spin_index][g_index];
238 }
239 }
240 has_previous_ = true;
241
242 // If we have history, do the Broyden update (smooth G-vectors only)
243 if (iterations_used > 0 && !delta_residuals_.empty()) {
244 int actual_history = static_cast<int>(delta_residuals_.size());
245
246 /**
247 * Build Gram matrix β_{ij} = ⟨ΔF_i|ΔF_j⟩_metric (preconditioned delta residuals).
248 * One side of each pairwise dot product uses the precomputed metric-scaled
249 * history entry (metric_scaled_delta_residuals_), so the metric weight is
250 * applied once per history entry (O(history_depth)) rather than once per
251 * pair (O(history_depth^2)) -- see build_metric_scaled_copy() below. The
252 * actual reduction is a plain, branch-free complex dot product
253 * (q3_complex_conjugate_dot_product, BLAS Level-1 style).
254 */
255 std::vector<Real> gram_matrix(actual_history * actual_history, 0.0);
256 for (int history_row = 0; history_row < actual_history; history_row++) {
257 for (int history_column = history_row; history_column < actual_history;
258 history_column++) {
259 Real dot =
260 dot_product_of_scaled_and_raw(metric_scaled_delta_residuals_[history_row],
261 delta_residuals_[history_column],
262 number_of_spin_components);
263 gram_matrix[history_row + history_column * actual_history] = dot;
264 gram_matrix[history_column + history_row * actual_history] = dot;
265 }
266 }
267
268 /**
269 * No diagonal regularization: the Gram matrix is inverted as built, with no w0²
270 * added. Two mechanisms cover the near-singular case instead -- DSYTRF's
271 * Bunch-Kaufman pivoting, and the fall-back to linear mixing on a non-zero
272 * LAPACK info below. Adding w0² was incorrect and interacted badly with the
273 * normalization that has also been removed.
274 */
275
276 /**
277 * Invert β via the LAPACK symmetric-indefinite path: DSYTRF factorizes
278 * with Bunch-Kaufman pivoting, DSYTRI inverts from that factorization
279 * (DSYTRI's contract is to consume DSYTRF's output). Not Cholesky: β is
280 * only positive SEMI-definite once the stored ΔF go nearly linearly
281 * dependent.
282 */
283 std::vector<int> pivot_indices(actual_history);
284 std::vector<Real> lapack_work(actual_history);
285 int info;
286
287 dsytrf_("U",
288 &actual_history,
289 gram_matrix.data(),
290 &actual_history,
291 pivot_indices.data(),
292 lapack_work.data(),
293 &actual_history,
294 &info);
295 if (info != 0) {
296 log::warn("Broyden mixing: DSYTRF failed (info={}), falling back to linear", info);
297 apply_linear_mixing(
298 rho_in_g, preconditioned_residual, number_of_spin, number_of_g_vectors);
299 log_diagnostics(actual_history);
300 return;
301 }
302
303 dsytri_("U",
304 &actual_history,
305 gram_matrix.data(),
306 &actual_history,
307 pivot_indices.data(),
308 lapack_work.data(),
309 &info);
310 if (info != 0) {
311 log::warn("Broyden mixing: DSYTRI failed (info={}), falling back to linear", info);
312 apply_linear_mixing(
313 rho_in_g, preconditioned_residual, number_of_spin, number_of_g_vectors);
314 log_diagnostics(actual_history);
315 return;
316 }
317
318 // Symmetrize the inverse (DSYTRI only fills upper triangle)
319 for (int history_row = 0; history_row < actual_history; history_row++) {
320 for (int history_column = history_row + 1; history_column < actual_history;
321 history_column++) {
322 gram_matrix[history_column + history_row * actual_history] =
323 gram_matrix[history_row + history_column * actual_history];
324 }
325 }
326
327 /**
328 * Compute projection γ_i = Σ_j β⁻¹_{ij} · ⟨ΔF_j | F_prec⟩_metric
329 * The projection uses the preconditioned current residual, because that is the
330 * space the history is stored in -- both sides of ⟨ΔF_j|F⟩ must live in the
331 * same space for β⁻¹ to be the right operator to apply.
332 * Reuses the same metric_scaled_delta_residuals_ entries built above --
333 * no additional metric-weight application needed here.
334 */
335 std::vector<Real> projection(actual_history);
336 for (int history_index = 0; history_index < actual_history; history_index++) {
337 projection[history_index] =
338 dot_product_of_scaled_and_raw(metric_scaled_delta_residuals_[history_index],
339 preconditioned_residual,
340 number_of_spin_components);
341 }
342
343 /**
344 * Apply the Broyden update over the smooth G-vectors only. On a dual grid the
345 * hard G-vectors carry the augmentation charge; they stay outside the
346 * quasi-Newton subspace and are linear-mixed by the caller
347 * (ReciprocalSpaceMixer::mix) instead:
348 * ρ_in -= Σ γ_i · Δρ_i (density correction in density space)
349 * F_prec -= Σ γ_i · ΔF_i (preconditioned residual correction)
350 * Each inner update is a scaled vector addition (y += alpha*x), so it maps
351 * directly onto BLAS ZAXPY rather than a hand-rolled per-element loop.
352 */
353 const int unit_stride = 1;
354 for (int history_index = 0; history_index < actual_history; history_index++) {
355 Real gamma = 0.0;
356 for (int inner_index = 0; inner_index < actual_history; inner_index++) {
357 gamma += gram_matrix[inner_index + history_index * actual_history] *
358 projection[inner_index];
359 }
360 Complex negative_gamma(-gamma, 0.0);
361
362 for (int spin_index = 0; spin_index < number_of_spin; spin_index++) {
363 blas::zaxpy_(&smooth_g_vector_count,
364 &negative_gamma,
365 delta_inputs_[history_index].data[spin_index].data(),
366 &unit_stride,
367 rho_in_g[spin_index].data(),
368 &unit_stride);
369 blas::zaxpy_(&smooth_g_vector_count,
370 &negative_gamma,
371 delta_residuals_[history_index].data[spin_index].data(),
372 &unit_stride,
373 preconditioned_residual.data[spin_index].data(),
374 &unit_stride);
375 }
376 }
377 }
378
379 /**
380 * Final mixing step: ρ_new = ρ_in + α · F_prec (smooth G-vectors only).
381 * Hard G-vectors are outside the Broyden subspace and are handled exclusively by
382 * the outer ReciprocalSpaceMixer::mix() via beta-weighted linear mixing. Limiting
383 * this call to smooth_g_vector_count prevents double-application of linear mixing
384 * to hard G.
385 */
386 apply_linear_mixing(
387 rho_in_g, preconditioned_residual, number_of_spin, smooth_g_vector_count);
388 log_diagnostics(static_cast<int>(delta_residuals_.size()));
389 }
390
391 void reset() override {
392 iteration_counter_ = 0;
393 delta_residuals_.clear();
394 delta_inputs_.clear();
395 metric_scaled_delta_residuals_.clear();
396 has_previous_ = false;
397 best_residual_norm_ = -1.0;
398 stagnation_counter_ = 0;
399 }
400
401 [[nodiscard]] std::string name() const override {
402 return "broyden";
403 }
404
405 private:
406 int iteration_counter_{0};
407
408 struct MixingRecord {
409 std::vector<std::vector<Complex>> data;
410 };
411
412 void apply_linear_mixing(std::vector<std::vector<Complex>>& rho_in_g,
413 const MixingRecord& residual,
414 int number_of_spin,
415 int number_of_g_vectors) {
416 const int unit_stride = 1;
417 Complex alpha(mixing_parameter_, 0.0);
418 for (int spin_index = 0; spin_index < number_of_spin; spin_index++) {
419 /**
420 * Charge channel (spin 0): start from first_nonzero_g_index_ to preserve the
421 * G=0 component (total charge). For standard (augmented) mixing, the G=0
422 * residual is always zero (both input and output carry the same total charge
423 * N_el/Ω), so this has no effect. For smooth-only mixing (US PP), the smooth
424 * wf density has N_smooth < N_el at G=0; skipping G=0 prevents charge drift
425 * through the Broyden linear mixing step.
426 * Magnetization channel (spin 1 in the (total, m) representation): G=0 is the
427 * total moment and MUST be mixed — the moment is not conserved and skipping
428 * it freezes moment relaxation entirely.
429 */
430 int start_g_index = (spin_index == 0) ? first_nonzero_g_index_ : 0;
431 int count = number_of_g_vectors - start_g_index;
432 // rho_in_g += mixing_parameter_ * residual (BLAS ZAXPY over the active range).
433 blas::zaxpy_(&count,
434 &alpha,
435 residual.data[spin_index].data() + start_g_index,
436 &unit_stride,
437 rho_in_g[spin_index].data() + start_g_index,
438 &unit_stride);
439 }
440 }
441
442 /**
443 * Per-(spin, G) scale factor of the Hartree-metric inner product implemented by
444 * ReciprocalSpaceMixer::compute_metric_dot_product() -- see that function's doc
445 * comment for the metric definition. Exposed here so the scale can be baked into
446 * a history vector ONCE (see build_metric_scaled_copy()) instead of being
447 * recomputed on every pairwise dot product in the O(history_depth^2) Gram matrix
448 * loop. Called only O(history_depth) times per iteration (once per new history
449 * entry), so the per-element branch here is not performance-sensitive.
450 */
451 Real metric_scale_factor(int spin_index, int g_index) const {
452 Real gamma_factor = is_gamma_only_ ? 2.0 : 1.0;
453 if (spin_index == 0) {
454 return (g_index < first_nonzero_g_index_)
455 ? 0.0
456 : charge_metric_weights_[g_index] * gamma_factor;
457 }
458 Real g_factor = (g_index < first_nonzero_g_index_) ? 1.0 : gamma_factor;
459 return magnetization_metric_weight_ * g_factor;
460 }
461
462 /**
463 * Build a metric-scaled copy of a MixingRecord: scaled[s][g] = metric_scale_factor(s,g)
464 * * record[s][g]. Since the metric weight is real, w(g)*Re(conj(a)*b) and
465 * Re(conj(w(g)*a)*b) are the same value mathematically, so scaling one side of a
466 * pairwise dot product reproduces the same weighted inner product as scaling neither
467 * side and applying the weight per-element inside the dot loop -- see
468 * dot_product_of_scaled_and_raw(). NOT guaranteed bit-identical to the original
469 * per-pair-weighted formulation (floating-point multiplication does not distribute
470 * exactly over addition for arbitrary weights), only mathematically equivalent to
471 * numerical precision -- see this branch's regression tests for the tolerance this
472 * is validated to. Charge-channel entries below first_nonzero_g_index_ are zeroed
473 * (not skipped), which contributes exact 0.0 terms to the subsequent dot-product sum,
474 * matching the ranged sum used elsewhere in this file.
475 */
476 MixingRecord
477 build_metric_scaled_copy(const MixingRecord& record, int number_of_spin, int g_count) const {
478 MixingRecord scaled;
479 scaled.data.resize(number_of_spin);
480 for (int spin_index = 0; spin_index < number_of_spin; spin_index++) {
481 scaled.data[spin_index].resize(g_count);
482 for (int g_index = 0; g_index < g_count; g_index++) {
483 scaled.data[spin_index][g_index] =
484 metric_scale_factor(spin_index, g_index) * record.data[spin_index][g_index];
485 }
486 }
487 return scaled;
488 }
489
490 /**
491 * ⟨scaled_a | raw_b⟩ in the mixing metric, given scaled_a is already
492 * metric-scaled (see build_metric_scaled_copy()). Reduces to a plain complex
493 * conjugate dot product per spin channel -- a BLAS Level-1-style operation
494 * (q3_complex_conjugate_dot_product, same helper used by
495 * layer_0_core/execution/serial.hpp) with no per-element weight lookup or
496 * branch, unlike ReciprocalSpaceMixer::compute_metric_dot_product().
497 */
498 Real dot_product_of_scaled_and_raw(const MixingRecord& scaled_a,
499 const MixingRecord& raw_b,
500 int number_of_spin) const {
501 Real result = 0.0;
502 for (int spin_index = 0; spin_index < number_of_spin; spin_index++) {
503 int g_count = static_cast<int>(scaled_a.data[spin_index].size());
505 g_count, scaled_a.data[spin_index].data(), raw_b.data[spin_index].data());
506 result += dot.real();
507 }
508 return result;
509 }
510
511 std::vector<MixingRecord> delta_residuals_;
512 std::vector<MixingRecord> delta_inputs_;
513 std::vector<MixingRecord> metric_scaled_delta_residuals_;
514 MixingRecord previous_preconditioned_residual_;
515 MixingRecord previous_input_;
516 bool has_previous_{false};
517 Real best_residual_norm_{-1.0};
518 int stagnation_counter_{0};
519};
520
521} // namespace q3
void reset() override
Definition broyden.hpp:391
BroydenMixer(Real mixing_parameter=0.7, int history_depth=8)
Definition broyden.hpp:41
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 broyden.hpp:64
std::string name() const override
Definition broyden.hpp:401
int history_depth_
Definition init.hpp:93
int history_depth() const
Definition init.hpp:76
Real mixing_parameter_
Definition init.hpp:92
Real last_residual_norm_
Definition init.hpp:94
Real mixing_parameter() const
Definition init.hpp:73
std::vector< Real > charge_metric_weights_
Definition init.hpp:205
std::shared_ptr< Preconditioner > preconditioner_
Definition init.hpp:206
Real magnetization_metric_weight_
Definition init.hpp:207
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
Real dot(const Matrix3x3 &vectors, int first, int second)
The dot product of rows first and second of vectors.
std::complex< double > q3_complex_conjugate_dot_product(int n, const std::complex< double > *x, const std::complex< double > *y)
Definition blas.hpp:100
void zaxpy_(const int *n, const std::complex< double > *alpha, const std::complex< double > *x, const int *incx, std::complex< double > *y, const int *incy)
void dsytri_(const char *uplo, const int *n, double *a, const int *lda, const int *ipiv, double *work, int *info)
void dsytrf_(const char *uplo, const int *n, double *a, const int *lda, int *ipiv, double *work, const int *lwork, int *info)
void debug(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:179
void warn(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:187
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
double Real
std::complex< double > Complex