TRIQS/triqs_modest 3.3.0
Modular Electronic Structure Toolkit
Loading...
Searching...
No Matches
double_counting.cpp
Go to the documentation of this file.
1// Copyright (c) 2025--present, The Simons Foundation
2// This file is part of TRIQS/modest and is licensed under the terms of GPLv3 or later.
3// SPDX-License-Identifier: GPL-3.0-or-later
4// See LICENSE in the root of this distribution for details.
5
6#include <utility>
9
10using nda::range;
11
12namespace triqs::modest {
13
14 //------------------------------------------------------------------------------------
16 std::pair<double, double> dc_formulas(std::string const method, double const N_tot, double const N_sigma, long const n_orb, double const U,
17 double const J) {
18 double Mag = N_sigma - (N_tot - N_sigma);
19 double L_orbit = 0.5 * (double(n_orb) - 1.0);
20 if (method == "cFLL") {
21 return {(U * (N_tot - 0.5) - 0.5 * J * (N_tot - 1.0)), 0.5 * U * N_tot * (N_tot - 1.0) - 0.5 * J * N_tot * (0.5 * N_tot - 1.0)};
22 } else if (method == "sFLL") {
23 return {(U * (N_tot - 0.5) - J * (N_sigma - 0.5)),
24 0.5 * U * N_tot * (N_tot - 1.0) - 0.5 * J * N_tot * (N_tot * 0.5 - 1.0) - 0.25 * J * Mag * Mag};
25 } else if (method == "cAMF") {
26 double C = (U + 2.0 * J * L_orbit) / (2 * L_orbit + 1.0);
27 return {
28 (U * N_tot - 0.5 * N_tot * C), // DC
29 (0.5 * U - 0.25 * C) * N_tot * N_tot //Energy
30 };
31 } else if (method == "sAMF") {
32 return {(U * N_tot - ((U + 2.0 * L_orbit * J) / (2.0 * L_orbit + 1.0)) * N_sigma), // DC
33 0.5 * U * N_tot * N_tot - 0.25 * ((U + 2.0 * L_orbit * J) / (2.0 * L_orbit + 1.0)) * N_tot * N_tot // Energy
34 - 0.25 * ((U + 2.0 * L_orbit * J) / (2.0 * L_orbit + 1.0)) * Mag * Mag};
35 } else if (method == "cHeld") {
36 double U_mean = (U + (n_orb - 1) * (U - 2 * J) + (n_orb - 1) * (U - 3 * J)) / (2 * n_orb - 1);
37 return {
38 (U_mean * (N_tot - 0.5)), //DC
39 0.5 * U_mean * N_tot * (N_tot - 1.0) //Energy
40 };
41 } else {
42 throw std::runtime_error("Not implemented! Complain to the developers");
43 }
44 }
45
46 //------------------------------------------------------------------------------------
47 // Compute total density N_total and per-spin densities N_per_sigma from a density matrix.
48 //
49 // The spin_kind determines how per-spin densities are treated:
50 // - Polarized: N_per_sigma retains the actual per-spin traces from the density matrix.
51 // This allows spin-dependent DC formulas (sFLL, sAMF) to produce
52 // different corrections for each spin channel.
53 // - NonPolarized: N_per_sigma is overridden to N_total/2 for both spins, making
54 // spin-dependent formulas reduce to their charge-only counterparts.
55 // - NonColinear: Single spin channel (n_sig=1); N_per_sigma is set to N_total/2
56 // so that dc_formulas sees the per-spin density as half the total.
57 static std::pair<double, nda::vector<double>> get_total_density(spin_kind_e spin_kind,
58 nda::array<nda::matrix<dcomplex>, 2> const &density_matrix) {
59 auto [n_blocks, n_sig] = density_matrix.shape();
60 auto N_per_sigma = nda::zeros<double>(n_sig);
61 for (auto sigma : range(n_sig))
62 for (auto bl : range(n_blocks)) N_per_sigma(sigma) += real(trace(density_matrix(bl, sigma)));
63 auto N_total = stdr::fold_left(N_per_sigma, 0.0, std::plus<>());
64
65 switch (spin_kind) {
67 break;
69 for (auto sigma : range(n_sig)) N_per_sigma(sigma) = 0.5 * N_total;
70 break;
72 N_per_sigma(0) = 0.5 * N_total;
73 break;
74 }
75 return {N_total, N_per_sigma};
76 }
77
78 //------------------------------------------------------------------------------------
79 dc_solver::dc_solver(spin_kind_e spin_kind, std::string method, double U_int, double J_hund)
80 : _spin_kind{spin_kind}, n_sigma{n_sigma_from_spin_kind(spin_kind)}, method{std::move(method)}, U_int{U_int}, J_hund{J_hund} {};
81
82 //------------------------------------------------------------------------------------
83 nda::array<nda::matrix<dcomplex>, 2> dc_solver::get_density_matrix_from_gf(block_gf<imfreq, matrix_valued> const &gimp) {
84 auto n_blocks = gimp.size() / n_sigma;
85 auto density_matrix = nda::array<nda::matrix<dcomplex>, 2>(n_blocks, n_sigma);
86 for (auto bl : range(n_blocks))
87 for (auto sigma : range(n_sigma)) density_matrix(bl, sigma) = density(gimp[bl + sigma * n_blocks]);
88 return density_matrix;
89 }
90
91 //------------------------------------------------------------------------------------
92 std::vector<nda::matrix<dcomplex>> dc_solver::dc_self_energy(nda::array<nda::matrix<dcomplex>, 2> const &density_matrix) {
93 auto [N_total, N_per_sigma] = get_total_density(_spin_kind, density_matrix);
94 auto [n_blocks, n_sig] = density_matrix.shape();
95
96 long n_orb = 0;
97 for (auto bl : range(n_blocks)) n_orb += density_matrix(bl, 0).shape()[0];
98 if (_spin_kind == spin_kind_e::NonColinear) n_orb /= 2;
99
100 auto Sigma_DC = std::vector<nda::matrix<dcomplex>>(n_blocks * n_sig);
101 for (auto bl : range(n_blocks))
102 for (auto sigma : range(n_sig))
103 Sigma_DC[bl + sigma * n_blocks] = std::get<0>(dc_formulas(method, N_total, N_per_sigma(sigma), n_orb, U_int, J_hund))
104 * nda::eye<dcomplex>(density_matrix(bl, sigma).shape()[0]);
105 return Sigma_DC;
106 }
107
108 //------------------------------------------------------------------------------------
109 double dc_solver::dc_energy(nda::array<nda::matrix<dcomplex>, 2> const &density_matrix) {
110 auto [N_total, N_per_sigma] = get_total_density(_spin_kind, density_matrix);
111 auto [n_blocks, n_sig] = density_matrix.shape();
112
113 long n_orb = 0;
114 for (auto bl : range(n_blocks)) n_orb += density_matrix(bl, 0).shape()[0];
115 if (_spin_kind == spin_kind_e::NonColinear) n_orb /= 2;
116
117 auto E_DC = std::vector<double>(n_sig);
118 for (auto sigma : range(n_sig)) E_DC[sigma] = std::get<1>(dc_formulas(method, N_total, N_per_sigma(sigma), n_orb, U_int, J_hund));
119
120 // E_DC must be independent of sigma (Mag appears only as Mag^2 in all current formulas).
121 // Guard against future formulas where this assumption may not hold.
122 if (n_sig > 1)
123 for (auto sigma : range(1, n_sig))
124 if (std::abs(E_DC[sigma] - E_DC[0]) > 1.e-10)
125 throw std::runtime_error("dc_energy: E_DC depends on spin channel");
126
127 return E_DC[0];
128 }
129
130 //------------------------------------------------------------------------------------
131 std::vector<nda::matrix<dcomplex>> dc_solver::dc_self_energy(block_gf<imfreq, matrix_valued> const &gimp) {
132 return dc_self_energy(get_density_matrix_from_gf(gimp));
133 }
134
135 //------------------------------------------------------------------------------------
136 double dc_solver::dc_energy(block_gf<imfreq, matrix_valued> const &gimp) {
137 return dc_energy(get_density_matrix_from_gf(gimp));
138 }
139
140} // namespace triqs::modest
double dc_energy(block_gf< imfreq, matrix_valued > const &gimp)
Compute the double counting correction to the energy from a Green's function.
std::vector< nda::matrix< dcomplex > > dc_self_energy(block_gf< imfreq, matrix_valued > const &gimp)
Compute the double-counting self-energy from a Green's function.
dc_solver(spin_kind_e spin_kind, std::string method, double U_int, double J_hund)
Construct a double counting "solver".
std::pair< double, double > dc_formulas(std::string const method, double const N_tot, double const N_sigma, long const n_orb, double const U, double const J)
double counting formulas parameterized by density, U, and J
double density(one_body_elements_on_grid const &obe, double mu, block2_gf< Mesh, matrix_valued > const &Sigma_dynamic, nda::array< nda::matrix< dcomplex >, 2 > const &Sigma_static)
Compute the density of the lattice Green's function with a self-energy using Woodbury.
Definition density.hpp:92
long n_sigma_from_spin_kind(spin_kind_e sk)
Number of σ channels for a given spin_kind_e.
spin_kind_e
Kind of σ index.
static std::pair< double, nda::vector< double > > get_total_density(spin_kind_e spin_kind, nda::array< nda::matrix< dcomplex >, 2 > const &density_matrix)