TRIQS/triqs_modest 3.3.0
Brillouin zone summation
Loading...
Searching...
No Matches
downfolding.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 "./downfolding.hpp"
7//#include "triqs_modest/ibz_symmetry_ops.hpp"
8#include <ranges>
9#include "utils/defs.hpp"
10//#include "utils/to_vector.hpp"
11//#include "utils/h5_proxy.hpp"
12//#include "utils/graph_algo.hpp"
13//#include "utils/unitary_matrix.hpp"
14// ranges::to : gcc >= 14 https://godbolt.org/z/8TofKvb5a
15
16using namespace triqs;
17
18namespace triqs::modest {
19
20 // -------------------------------------------------------------------------------------------
21
22 // FIXME : we can easily avoid the copy with deducing this : pass self and forward it to res.
23 downfolding_projector downfolding_projector::rotate_local_basis(nda::array<nda::matrix<dcomplex>, 2> const &U) const {
24 auto res = *this;
25 auto n_k = res.P_k.extent(0);
26 auto n_sigma = res.P_k.extent(1);
27 for (auto isig : range(n_sigma)) {
28 auto decomp = range(U.extent(0)) | stdv::transform([&](auto &&m) { return U(m, isig).extent(0); }) | tl::to<std::vector>();
29 for (auto ik : range(n_k)) {
30 for (auto const &[a, sli] : enumerated_sub_slices(decomp)) {
31 // P <- dagger(U) * P
32 res.P_k(ik, isig, sli, r_all) = dagger(U(a, isig)) * matrix_const_view<dcomplex>{this->P_k(ik, isig, sli, r_all)};
33 // I can not use x.P(isig,k)(sli, r_all) because of the slice on the n_bands_per_k in this P }
34 }
35 }
36 }
37 return res;
38 }
39
40 // -------------------------------------------------------------------------------------------
41
42 one_body_elements_on_grid rotate_local_basis(nda::array<nda::matrix<dcomplex>, 2> const &U, one_body_elements_on_grid const &x) {
43 auto res = x;
44 res.P = x.P.rotate_local_basis(U);
45 if (res.ibz_symm_ops) res.ibz_symm_ops = rotate_local_basis(U, x.ibz_symm_ops.value());
46 return res;
47 }
48 // -------------------------------------------------------------------------------------------
49
50 one_body_elements_on_grid permute_local_space(std::vector<std::vector<long>> const &atom_partition, one_body_elements_on_grid const &obe) {
51 auto atom_indices = nda::flatten(atom_partition);
52
53 auto spin_kind = obe.C_space.spin_kind();
54 auto atomic_shells = atom_indices | stdv::transform([&](auto i) { return obe.C_space.atomic_shells()[i]; }) | tl::to<std::vector>();
55
56 auto rot_from_dft_to_local_basis = obe.C_space.rotation_from_dft_to_local_basis();
57 auto rot_from_sph_to_dft_basis = obe.C_space.rotation_from_spherical_to_dft_basis();
58 for (auto &&[iatom, atom] : enumerate(atom_indices)) {
59 rot_from_dft_to_local_basis(iatom, r_all) = obe.C_space.rotation_from_dft_to_local_basis()(atom, r_all);
60 rot_from_sph_to_dft_basis(iatom) = obe.C_space.rotation_from_spherical_to_dft_basis()(atom);
61 }
62
63 auto n_atoms = atom_partition.size();
64 auto n_sigma = obe.C_space.n_sigma();
65
66 auto irreps_per_atom = nda::array<std::vector<long>, 2>(n_atoms, n_sigma);
67 for (auto &&[ipart, partition] : enumerate(atom_partition)) {
68 if (partition.size() == 1) {
69 irreps_per_atom(ipart, r_all) = obe.C_space.atoms_block_decomposition()(partition[0], r_all);
70 } else {
71 irreps_per_atom(ipart, r_all) = std::vector{
72 stdr::fold_left(partition | stdv::transform([&](auto idx) { return obe.C_space.atomic_shells()[idx].dim; }), 0, std::plus<>())};
73 }
74 }
75
76 auto new_C_space = local_space{spin_kind, atomic_shells, irreps_per_atom, rot_from_dft_to_local_basis, rot_from_sph_to_dft_basis};
77
78 auto atom_decomp = obe.C_space.atomic_decomposition() | tl::to<std::vector>();
79
80 auto [n_k, n_sig, n_M, n_nu] = obe.P.P_k.shape();
81
82 auto P_k_list = atom_decomp | stdv::transform([&](auto dim) { return nda::zeros<dcomplex>(n_k, n_sig, dim, n_nu); }) | tl::to<std::vector>();
83 for (auto &&[atom, sli] : enumerated_sub_slices(atom_decomp)) {
84 P_k_list[atom](r_all, r_all, r_all, r_all) = obe.P.P_k(r_all, r_all, sli, r_all);
85 }
86
87 auto new_P_k_list = atom_indices | stdv::transform([&](auto i) { return P_k_list[i]; }) | tl::to<std::vector>();
88
89 auto new_atom_decomp = new_C_space.atomic_decomposition() | tl::to<std::vector>();
90
91 auto P_k = nda::zeros<dcomplex>(n_k, n_sig, n_M, n_nu);
92 for (auto const &[atom, sli] : enumerated_sub_slices(new_atom_decomp)) {
93 P_k(r_all, r_all, sli, r_all) = new_P_k_list[atom](r_all, r_all, nda::range(0, new_atom_decomp[atom]), r_all);
94 }
95
96 auto n_bands_per_k = obe.P.n_bands_per_k;
97 downfolding_projector new_P{.spin_kind = spin_kind, .P_k = P_k, .n_bands_per_k = n_bands_per_k};
98
99 if (!obe.ibz_symm_ops) return {.H = obe.H, .C_space = new_C_space, .P = new_P, .ibz_symm_ops = {}};
100
101 auto ibz_symm_ops = obe.ibz_symm_ops.value();
102 auto new_ibz_symm_ops = ibz_symm_ops;
103 for (auto &&[isym, sym_op] : enumerate(new_ibz_symm_ops.ops)) {
104 sym_op.mats = atom_indices | stdv::transform([&](auto i) { return ibz_symm_ops.ops[isym].mats[i]; }) | tl::to<std::vector>();
105 sym_op.permutation = atom_indices | stdv::transform([&](auto i) { return ibz_symm_ops.ops[isym].permutation[i]; }) | tl::to<std::vector>();
106 }
107 return {.H = obe.H, .C_space = new_C_space, .P = new_P, .ibz_symm_ops = new_ibz_symm_ops};
108 }
109
110 // -------------------------------------------------------------------------------------------
111
112 nda::array<nda::matrix<dcomplex>, 2> impurity_levels(one_body_elements_on_grid const &obe) {
113 auto n_atoms = obe.C_space.n_atoms();
114 auto n_sigma = obe.C_space.n_sigma();
115 auto results_per_atom = nda::array<nda::matrix<dcomplex>, 2>{n_atoms, n_sigma};
116 // Hloc^{σ} = \sum_{k} P^{σ}_{mν}(k) H^{σ}_{νν'} P^{σ}_{m'ν'}(k)
117 for (auto const &[atom_idx, R_atom] : enumerated_sub_slices(obe.C_space.atomic_decomposition())) {
118 auto sh = obe.C_space.atomic_shells()[atom_idx];
119 auto M_a = sh.dim;
120 for (auto sigma : range(n_sigma)) {
121 auto hloc0 = nda::zeros<dcomplex>(M_a, M_a);
122 for (auto ik : range(obe.H.n_k())) { // loop over k-points
123 auto P = obe.P.P(sigma, ik)(R_atom, r_all);
124 hloc0 += obe.H.k_weights(ik) * P * obe.H.H(sigma, ik) * dagger(P);
125 }
126 results_per_atom(atom_idx, sigma) = hloc0;
127 }
128 }
129 if (auto const &S = obe.ibz_symm_ops; S) results_per_atom = S->symmetrize(results_per_atom);
130 // FIXME : why are we doing this ? If so, why did we compute all of them before ?
131 for (auto atom_idx : range(n_atoms)) {
132 if (auto inequiv = obe.C_space.first_shell_of_its_equiv_cls(atom_idx); atom_idx != inequiv) {
133 results_per_atom(atom_idx, r_all) = results_per_atom(inequiv, r_all);
134 }
135 }
136 auto result = nda::array<nda::matrix<dcomplex>, 2>{1, n_sigma};
137 for (auto sigma : range(n_sigma)) {
138 result(0, sigma) = nda::zeros<dcomplex>(obe.C_space.dim(), obe.C_space.dim());
139 for (auto const &[atm, r_atom] : enumerated_sub_slices(obe.C_space.atomic_decomposition())) {
140 result(0, sigma)(r_atom, r_atom) = results_per_atom(atm, sigma);
141 }
142 }
143 return result;
144 }
145 // -------------------------------------------------------------------------------------------
146
147 // fnt case to be implemented
148 //nda::array<nda::matrix<dcomplex>, 2> impurity_levels(one_body_elements const &one_body) {
149 // // return H_k.get_R({0, 0, 0}); // GET R = 0 member.
150 // }
151
152 // -------------------------------------------------------------------------
153
154 nda::array<dcomplex, 3> detail::G0_C_k_sigma(one_body_elements_on_grid const &obe, double mu, long k_idx, long sigma,
155 std::vector<dcomplex> const &omegas, bool mu_derivative) {
156 // Using Woodbury formula
157
158 auto n_nu = obe.H.N_nu(sigma, k_idx);
159 auto R_nu = nda::range(n_nu);
160 auto M = obe.C_space.dim();
161
162 // Compute Dinv(omega, nu) = 1/(om + mu - Hk(nu))
163 auto n_omega = omegas.size();
164 auto Dinv = nda::matrix<dcomplex>(n_omega, n_nu);
165 auto Hk = obe.H.H(sigma, k_idx); // Hk[nu,nu']
166 constexpr auto sqr = [](auto x) { return x * x; };
167
168 // avoid the if in the inner loop
169 if (not mu_derivative) {
170 for (auto [n, om] : enumerate(omegas))
171 for (auto nu : R_nu) Dinv(n, nu) = 1 / (om + mu - Hk(nu, nu));
172 } else {
173 for (auto [n, om] : enumerate(omegas))
174 for (auto nu : R_nu) Dinv(n, nu) = -1 / sqr(om + mu - Hk(nu, nu));
175 }
176 // Compute M[nu, (alpha, m), (alpha', m')] = P[(alpha, m), nu ] conj(P)[(alpha', m'), nu ]
177 // Compute M[nu, m, m'] = P[m, nu ] conj(P)[m', nu ]
178 auto PP = nda::zeros<dcomplex>(n_nu, M, M);
179 auto P = obe.P.P(sigma, k_idx);
180 for (auto m : range(M))
181 for (auto mp : range(M))
182 for (auto nu : R_nu) PP(nu, m, mp) = P(m, nu) * conj(P(mp, nu));
183
184 // Compute Y [ w, m, m'] = D[w, nu] * PP[nu, m, m']
185 // Sum over nu = matmul.
186 auto Y = nda::zeros<dcomplex>(n_omega, M, M);
187 auto M_asmat = cmat_vt{nda::group_indices_view(PP, nda::idx_group<0>, nda::idx_group<1, 2>)};
188 auto Y_asmat = cmat_vt{nda::group_indices_view(Y, nda::idx_group<0>, nda::idx_group<1, 2>)};
189 nda::blas::gemm(1, Dinv, M_asmat, 0, Y_asmat);
190 return Y;
191 }
192
193} // namespace triqs::modest
Describe the atomic orbitals within downfolded space.
spin_kind_e spin_kind() const
nda::array< nda::matrix< dcomplex >, 1 > const & rotation_from_spherical_to_dft_basis() const
List of rotation matrices from spherical harmonics to dft specific orbital basis.
long n_sigma() const
Dimension of the σ index.
nda::array< nda::matrix< dcomplex >, 2 > const & rotation_from_dft_to_local_basis() const
List of all (a, sigma) local rotation matices that rotate the data.
nda::array< std::vector< long >, 2 > const & atoms_block_decomposition() const
List of all blocks spanning 𝓒 space -> atoms_block_decomposition.
auto atomic_decomposition() const
Generates [dimension of the atomic shell].
long dim() const
Dimension of the correlated space.
std::vector< atomic_orbs > const & atomic_shells() const
List of all atomic shells spanning the 𝓒 space.
long n_atoms() const
The number of atoms.
long first_shell_of_its_equiv_cls(long idx) const
Given the index idx of an atomic shell, return the index of the first atomic shell of its equivalence...
nda::array< nda::matrix< dcomplex >, 2 > impurity_levels(one_body_elements_on_grid const &obe)
Compute the local impurity levels from the single-particle dispersion.
std::vector< T > flatten(const std::vector< std::vector< T > > &nested)
Definition nda_supp.hpp:26
one_body_elements_on_grid rotate_local_basis(nda::array< nda::matrix< dcomplex >, 2 > const &U, one_body_elements_on_grid const &x)
Rotates the local basis of the downfolding projector.
one_body_elements_on_grid permute_local_space(std::vector< std::vector< long > > const &atom_partition, one_body_elements_on_grid const &obe)
static constexpr auto r_all
Definition defs.hpp:40
nda::matrix_view< dcomplex > cmat_vt
Definition defs.hpp:39
generator< std::pair< long, nda::range > > enumerated_sub_slices(auto sub_div)
nda::array< double, 1 > k_weights
k_weights[k_idx]
long n_k() const
Number of k points in the grid.
nda::matrix_const_view< dcomplex > H(long sigma, long k_idx) const
H^σ(k)_ν, returned as a MATRIX in (ν, ν)
The projector that downfolds the one-body dispersion (ν) onto local orbitals (m).
nda::matrix_const_view< dcomplex > P(long sigma, long k_idx) const
P^σ(k)_mν, returned as a matrix in (m ν)
nda::array< dcomplex, 4 > P_k
Pk[alpha][k_idx, σ', m_alpha, nu].
nda::array< long, 2 > n_bands_per_k
n_bands_per_k [k_idx, σ'] = # of nu
downfolding_projector rotate_local_basis(nda::array< nda::matrix< dcomplex >, 2 > const &U) const
Rotates the local basis of the downfolding projector.
A one-body elements struct where all of the underlying data exists on a fixed momentum grid.
C2PY_IGNORE std::optional< ibz_symmetry_ops > ibz_symm_ops
IBZ symmetrizer after a k-sum.