TRIQS/triqs_modest 3.3.0
Modular Electronic Structure Toolkit
Loading...
Searching...
No Matches
hamiltonians.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 "./hamiltonians.hpp"
7#include <itertools/enumerate.hpp>
8#include <triqs/operators/many_body_operator.hpp>
9#include <algorithm>
10#include "utils/defs.hpp"
11
12namespace triqs {
13
14 namespace detail {
15
16 auto n(auto const &x) { return operators::n(x.first, x.second); };
17 auto c_dag(auto const &x) { return operators::c_dag(x.first, x.second); };
18 auto c(auto const &x) { return operators::c(x.first, x.second); };
19
20 //-----------------------------------------
21
22 double three_j_symbol(std::tuple<long, long, long, long, long, long> jms) {
23 auto [j1, m1, j2, m2, j3, m3] = jms;
24 auto fact = [](auto x) { return std::tgamma(x + 1); };
25 if ((m1 + m2 + m3 != 0) || (m1 < -j1) || (m1 > j1) || (m2 < -j2) || (m2 > j2) || (m3 < -j3) || (m3 > j3) || (j3 > j1 + j2)
26 || j3 < std::abs(j1 - j2)) {
27 return 0.0;
28 }
29 auto three_j_sym = ((j1 - j2 - m3) % 2) ? -1.0 : 1.0;
30 three_j_sym *= sqrt((fact(j1 + j2 - j3) * fact(j1 - j2 + j3) * fact(-j1 + j2 + j3)) / fact(j1 + j2 + j3 + 1));
31 three_j_sym *= sqrt(fact(j1 - m1) * fact(j1 + m1) * fact(j2 - m2) * fact(j2 + m2) * fact(j3 - m3) * fact(j3 + m3));
32
33 auto t_min = std::max({j2 - j3 - m1, j1 - j3 + m2, static_cast<long>(0)});
34 auto t_max = std::min({j1 - m1, j2 + m2, j1 + j2 - j3});
35 auto t_sum = 0.0;
36 for (auto t : range(t_min, t_max + 1)) {
37 t_sum += ((t % 2) ? -1.0 : 1.0)
38 / (fact(t) * fact(j3 - j2 + m1 + t) * fact(j3 - j1 - m2 + t) * fact(j1 + j2 - j3 - t) * fact(j1 - m1 - t) * fact(j2 + m2 - t));
39 }
40 three_j_sym *= t_sum;
41 return three_j_sym;
42 }
43
45 double angular_matrix_element(long l, long k, long m1, long m2, long m3, long m4) {
46 auto squared = [](auto x) { return x * x; };
47
48 auto ang_mat_ele = 0.0;
49 for (auto q : range(-k, k + 1)) {
50 ang_mat_ele += three_j_symbol({l, -m1, k, q, l, m3}) * three_j_symbol({l, -m2, k, -q, l, m4}) * (((m1 + q + m2) % 2) ? -1.0 : 1.0);
51 }
52 ang_mat_ele *= squared(2.0 * static_cast<double>(l) + 1) * squared(three_j_symbol({l, 0, k, 0, l, 0}));
53 return ang_mat_ele;
54 }
55
56 nda::array<double, 1> radial_integrals(long l, double U_int, double J_hund) {
57 auto F = nda::zeros<double>(l + 1);
58 if (l == 1) {
59 F(0) = U_int;
60 F(1) = 5 * J_hund;
61 } else if (l == 2) {
62 F(0) = U_int;
63 F(1) = 14.0 * J_hund / (1.0 + 0.63);
64 F(2) = 0.63 * F(1);
65 } else if (l == 3) {
66 F(0) = U_int;
67 F(1) = (6435.0 * J_hund) / (286.0 + 195.0 * 451.0 / 675.0 + 250.0 * 1001.0 / 2025.0);
68 F(2) = 451.0 * F(1) / 675.0;
69 F(3) = 1001.0 * F(1) / 2025.0;
70 }
71 return F;
72 }
73 } // namespace detail
74
75 std::map<op_name, op_name> make_op_map(auto const &spin_names, auto const &orb_dims) {
76 std::map<op_name, op_name> op_map;
77 for (auto const &spin : spin_names) {
78 long flat_idx = 0;
79 for (auto [bl_idx, bl_size] : enumerate(orb_dims)) {
80 for (auto orb : range(bl_size)) { op_map[{spin, flat_idx++}] = {fmt::format("{}_{}", spin, bl_idx), orb}; }
81 }
82 }
83 return op_map;
84 }
85
86 operators::many_body_operator rename_op(operators::many_body_operator const &op, std::map<op_name, op_name> const &op_map) {
87
88 auto rename_monomial = [&](auto const &m) -> operators::many_body_operator {
89 operators::many_body_operator nop = operators::many_body_operator(1.0);
90 for (auto const &b : m) {
91 auto name = op_map.at({std::get<std::string>(b.indices[0]), std::get<long>(b.indices[1])});
92 nop *= (b.dagger) ? detail::c_dag(name) : detail::c(name);
93 }
94 return nop;
95 };
96
97 auto new_op = operators::many_body_operator();
98 for (auto const &term : op) { new_op += term.coef * rename_monomial(term.monomial); }
99
100 return new_op;
101 }
102
104 std::pair<nda::matrix<double>, nda::matrix<double>> U_matrix_kanamori(long n_orb, double U_int, double U_prime, double J_hund) {
105 auto Umat = nda::zeros<double>(n_orb, n_orb);
106 auto Upmat = nda::zeros<double>(n_orb, n_orb);
107 for (auto &&[m, mp] : product(range(n_orb), range(n_orb))) {
108 if (m == mp) {
109 Upmat(m, mp) = U_int;
110 } else {
111 Umat(m, mp) = U_prime - J_hund;
112 Upmat(m, mp) = U_prime;
113 }
114 }
115 return {Umat, Upmat};
116 }
117
118 nda::array<double, 4> U_matrix_slater_spherical(long l, double U_int, double J_hund) {
119 auto U_matrix = nda::zeros<double>(2 * l + 1, 2 * l + 1, 2 * l + 1, 2 * l + 1);
120 auto m_range = nda::range(-l, l + 1);
121 auto radial_integrals = detail::radial_integrals(l, U_int, J_hund);
122 for (auto [n, F] : itertools::enumerate(radial_integrals)) {
123 auto k = 2 * n;
124 for (auto [m1, m2, m3, m4] : product(m_range, m_range, m_range, m_range)) {
125 U_matrix(m1 + l, m2 + l, m3 + l, m4 + l) += F * detail::angular_matrix_element(l, k, m1, m2, m3, m4);
126 }
127 }
128 return U_matrix;
129 }
130
131 nda::array<dcomplex, 4> rotate_U_matrix_slater(nda::array<double, 4> const &Uspherical, nda::matrix<dcomplex> sph_to_local) {
132 auto s2lC = conj(sph_to_local);
133 auto s2lT = transpose(sph_to_local);
134 auto N = Uspherical.extent(0);
135 auto U_local = nda::zeros<dcomplex>(N, N, N, N);
136 for (auto [i, k, n, p, j, q, m, o] : product(range(N), range(N), range(N), range(N), range(N), range(N), range(N), range(N))) {
137 auto left = s2lC(i, j) * s2lC(k, q);
138 auto right = (s2lT(m, n)) * s2lT(o, p);
139 U_local(i, k, n, p) += left * Uspherical(j, q, m, o) * right;
140 }
141 return U_local;
142 }
143
144 nda::array<dcomplex, 4> U_matrix_slater_local(long l, nda::matrix<dcomplex> sph_to_local, double U_int, double J_hund) {
145 auto Uspherical = U_matrix_slater_spherical(l, U_int, J_hund); // construct U in spherical basis
146 return rotate_U_matrix_slater(Uspherical, sph_to_local); // rotate spherical U to local basis
147 }
148
149 operators::many_body_operator h_int_kanamori(nda::matrix<double> const &Umat, nda::matrix<double> const &Upmat, double const &J_hund,
150 int const &n_orb, std::vector<std::string> const &spin_names, kanamori_params const &params) {
151
152 auto h_int = operators::many_body_operator();
153
154 // density-density terms
155 for (auto &&[s1, s2] : itertools::product(spin_names, spin_names)) {
156 for (auto &&[a1, a2] : product(range(n_orb), range(n_orb))) {
157 h_int += 0.5 * ((s1 == s2) ? Umat(a1, a2) : Upmat(a1, a2)) * detail::n(std::make_pair(s1, a1)) * detail::n(std::make_pair(s2, a2));
158 }
159 }
160
161 // spin-flip terms
162 if (params.spin_flip) {
163 for (auto &&[s1, s2] : itertools::product(spin_names, spin_names)) {
164 if (s1 == s2) continue;
165 for (auto &&[a1, a2] : product(range(n_orb), range(n_orb))) {
166 if (a1 == a2) continue;
167 h_int += -0.5 * J_hund * detail::c_dag(std::make_pair(s1, a1)) * detail::c(std::make_pair(s2, a1)) * detail::c_dag(std::make_pair(s2, a2))
168 * detail::c(std::make_pair(s1, a2));
169 }
170 }
171 }
172
173 // pair-hopping terms
174 if (params.pair_hopping) {
175 for (auto &&[s1, s2] : itertools::product(spin_names, spin_names)) {
176 if (s1 == s2) continue;
177 for (auto &&[a1, a2] : product(range(n_orb), range(n_orb))) {
178 if (a1 == a2) continue;
179 h_int += 0.5 * J_hund * detail::c_dag(std::make_pair(s1, a1)) * detail::c_dag(std::make_pair(s2, a1)) * detail::c(std::make_pair(s2, a2))
180 * detail::c(std::make_pair(s1, a2));
181 }
182 }
183 }
184 return h_int;
185 }
186
187 operators::many_body_operator h_int_density(nda::matrix<double> const &Umat, nda::matrix<double> const &Upmat, double const &J_hund,
188 int const &n_orb, std::vector<std::string> const &spin_names) {
189 kanamori_params params{.spin_flip = false, .pair_hopping = false};
190 return h_int_kanamori(Umat, Upmat, J_hund, n_orb, spin_names, params);
191 }
192
193 // ----------------------------------------------------
194
195 operators::many_body_operator h_int_slater(nda::array<dcomplex, 4> const &Umatrix, int const &n_orb, std::vector<std::string> const &spin_names) {
196 auto h_int = operators::many_body_operator();
197 for (auto &&[s1, s2] : itertools::product(spin_names, spin_names)) {
198 for (auto [m1, m2, m3, m4] : product(range(n_orb), range(n_orb), range(n_orb), range(n_orb))) {
199 h_int += 0.5 * real(Umatrix(m1, m2, m3, m4)) * detail::c_dag(std::make_pair(s1, m1)) * detail::c_dag(std::make_pair(s2, m2))
200 * detail::c(std::make_pair(s2, m4)) * detail::c(std::make_pair(s1, m3));
201 }
202 }
203 return h_int;
204 }
205
206 // ----------------------------------------------------
207 operators::many_body_operator make_kanamori(std::vector<std::string> const &tau_names, std::vector<long> const &dim_gamma, double U_int,
208 double U_prime, double J_hund, bool spin_flip, bool pair_hopping) {
209
210 // compute number of orbitals
211 long n_orb = stdr::fold_left(dim_gamma, 0, std::plus<>());
212
213 // construct operator mapping
214 auto op_map = make_op_map(tau_names, dim_gamma);
215
216 // construct kanamori U matrices
217 auto [Umat, Upmat] = U_matrix_kanamori(n_orb, U_int, U_prime, J_hund);
218
219 // construct kanamori params
220 kanamori_params params{.spin_flip = spin_flip, .pair_hopping = pair_hopping};
221
222 // construct h_int and rename operators
223 return rename_op(h_int_kanamori(Umat, Upmat, J_hund, n_orb, tau_names, params), op_map);
224 }
225
226 operators::many_body_operator make_density_density(const std::vector<std::string> &tau_names, const std::vector<long> &dim_gamma, double U_int,
227 double U_prime, double J_hund) {
228 // make kanamori without spin-flip and pair-hopping
229 return make_kanamori(tau_names, dim_gamma, U_int, U_prime, J_hund, false, false);
230 }
231
232 operators::many_body_operator make_slater(std::vector<std::string> const &tau_names, std::vector<long> const &dim_gamma, double U_int,
233 double J_hund, nda::matrix<dcomplex> const &spherical_to_dft,
234 std::optional<nda::matrix<dcomplex>> const &dft_to_local) {
235
236 // compute number of orbitals
237 long n_orb = stdr::fold_left(dim_gamma, 0, std::plus<>());
238
239 // construct operator mapping
240 auto op_map = make_op_map(tau_names, dim_gamma);
241
242 // construct U matrix and rotate to local basis
243 long l = (n_orb - 1) / 2;
244 auto transform = (dft_to_local) ? spherical_to_dft * dft_to_local.value() : spherical_to_dft;
245 auto U_matrix = U_matrix_slater_local(l, transform, U_int, J_hund);
246
247 // construct h_int and rename operators
248 return rename_op(h_int_slater(U_matrix, n_orb, tau_names), op_map);
249 }
250} // namespace triqs
std::pair< nda::matrix< double >, nda::matrix< double > > U_matrix_kanamori(long n_orb, double U_int, double U_prime, double J_hund)
construct the Kanamori two-index interaction matrix for parallel and anti-parallel spins.
operators::many_body_operator make_density_density(const std::vector< std::string > &tau_names, const std::vector< long > &dim_gamma, double U_int, double U_prime, double J_hund)
Construct a density-density interaction Hamiltonian.
operators::many_body_operator make_slater(std::vector< std::string > const &tau_names, std::vector< long > const &dim_gamma, double U_int, double J_hund, nda::matrix< dcomplex > const &spherical_to_dft, std::optional< nda::matrix< dcomplex > > const &dft_to_local)
Construct a Slater Hamiltonian.
nda::array< double, 4 > U_matrix_slater_spherical(long l, double U_int, double J_hund)
Construct a four-index Coulomb tensor in the basis of spherical harmonics.
nda::array< dcomplex, 4 > U_matrix_slater_local(long l, nda::matrix< dcomplex > sph_to_local, double U_int, double J_hund)
Construct a four-index Coulomb tensor in a specific orbital basis.
operators::many_body_operator make_kanamori(std::vector< std::string > const &tau_names, std::vector< long > const &dim_gamma, double U_int, double U_prime, double J_hund, bool spin_flip, bool pair_hopping)
Construct a Hubbard-Kanamori Hamiltonian.
operators::many_body_operator rename_op(operators::many_body_operator const &op, std::map< op_name, op_name > const &op_map)
operators::many_body_operator h_int_slater(nda::array< dcomplex, 4 > const &Umatrix, int const &n_orb, std::vector< std::string > const &spin_names)
operators::many_body_operator h_int_density(nda::matrix< double > const &Umat, nda::matrix< double > const &Upmat, double const &J_hund, int const &n_orb, std::vector< std::string > const &spin_names)
std::map< op_name, op_name > make_op_map(auto const &spin_names, auto const &orb_dims)
operators::many_body_operator h_int_kanamori(nda::matrix< double > const &Umat, nda::matrix< double > const &Upmat, double const &J_hund, int const &n_orb, std::vector< std::string > const &spin_names, kanamori_params const &params)
nda::array< dcomplex, 4 > rotate_U_matrix_slater(nda::array< double, 4 > const &Uspherical, nda::matrix< dcomplex > sph_to_local)