TRIQS/TRIQS 4.0.0
Researching Interacting Quantum Systems
Loading...
Searching...
No Matches
bz_integrators.hpp
1#pragma once
2
3#include "../utility/adaptive.hpp"
4#include "../utility/integrator.hpp"
5#include "../../gfs.hpp"
6
7#include <itertools/itertools.hpp>
8#include <mpi/mpi.hpp>
9#include <nda/nda.hpp>
10
11#include <algorithm>
12#include <array>
13#include <concepts>
14#include <functional>
15#include <iostream>
16#include <iterator>
17#include <numeric>
18#include <stdexcept>
19#include <utility>
20#include <vector>
21
22namespace triqs::experimental::lattice {
23
24 using namespace triqs::gfs;
25
30
37 namespace placeholders {
38
40 constexpr nda::clef::placeholder<0> kx; // global var, clang requests const(expr)
42 constexpr nda::clef::placeholder<1> ky;
44 constexpr nda::clef::placeholder<2> kz;
46 constexpr nda::clef::placeholder<3> w;
47
48 } // namespace placeholders
49
57 double tolerance = 1.e-3;
58 };
59
69 double tolerance = 1.e-3;
70 std::array<long, 3> k_grid = {10, 10, 10};
71 std::array<long, 3> delta_k_grid = {2, 2, 2};
72 std::array<long, 3> k_grid_max = {20, 20, 20};
73 bool run_adaptive = true;
74 bool run_ptr = true;
75 bool verbose = false;
76 };
77
78 namespace detail {
79 // helper to determine the return container dimension
80 int deduce_dim_from_expression(auto const &f_kw) {
81 namespace ph = placeholders;
82 auto f_w_temp = eval(f_kw, ph::kx = 0., ph::ky = 0., ph::kz = 0., ph::w = 0);
83 static_assert(not nda::clef::is_lazy<decltype(f_w_temp)>, "Integration expects a proper expression with placeholders.");
84 // check if this is a matrix or scalar type -- if scalar, return 1 for dim
85 return [&]() {
86 if constexpr (requires { f_w_temp.shape(); }) { // i.e. if this compiles ...
87 return f_w_temp.shape()[0]; // this is a matrix type with block dim
88 } else {
89 return 1; // this is a scalar type, will be handled as a 1D matrix
90 }
91 }();
92 }
93 } // namespace detail
94
95 //---------------------------------------------------------
96
112 template <typename T>
113 requires(std::convertible_to<T, dcomplex> or std::convertible_to<T, double>) // allow for real or imaginary mesh points or numbers
114 nda::array<dcomplex, 3> integrate_ptr(auto const &f_kw, std::vector<T> const &omega_values, std::array<long, 3> const &k_grid,
115 mpi::communicator comm) {
116
117 for (auto i : {0, 1, 2}) {
118 if (k_grid[i] <= 0) throw std::runtime_error{"Cannot use PTR integration with kgrid dim <= 0."};
119 }
120
121 namespace ph = placeholders;
122 int block_dim = detail::deduce_dim_from_expression(f_kw);
123 auto result = nda::zeros<dcomplex>(omega_values.size(), block_dim, block_dim); // container to return
124
125 // Determine the longest direction and apply MPI chunk to this dimension, otherwise provide a simple iterator
126 auto mpi_chunk_max = [&k_grid, comm](int kdim) {
127 // chunk the biggest one with MPI
128 if (kdim == std::distance(k_grid.begin(), std::ranges::max_element(k_grid))) { return mpi::chunk(nda::range(k_grid[kdim]), comm); }
129 return itertools::slice(nda::range(k_grid[kdim]), 0,
130 k_grid[kdim]); // otherwise complains about different return types
131 };
132
133// omp reduction operation for nda array
134#pragma omp declare reduction(array_add_c_3 : nda::array<dcomplex, 3> : omp_out += omp_in) \
135 initializer(omp_priv = nda::array<dcomplex, 3>(omp_orig.shape()))
136
137#pragma omp parallel for collapse(3) reduction(array_add_c_3 : result) default(none) \
138 shared(k_grid, omega_values, f_kw, ph::kx, ph::ky, ph::kz, ph::w, mpi_chunk_max)
139 for (auto ikx : mpi_chunk_max(0)) {
140 for (auto iky : mpi_chunk_max(1)) {
141 for (auto ikz : mpi_chunk_max(2)) {
142 double kx = ikx / double(k_grid[0]);
143 double ky = iky / double(k_grid[1]);
144 double kz = ikz / double(k_grid[2]);
145 auto f_w = eval(f_kw, ph::kx = kx, ph::ky = ky, ph::kz = kz);
146 for (auto &&[n, omega] : itertools::enumerate(omega_values)) result(n, nda::range::all, nda::range::all) += eval(f_w, ph::w = omega);
147 }
148 }
149 }
150 result = mpi::all_reduce(result, comm);
151
152 // apply normalization, divide by number of k-points on integrated grid
153 result /= double(k_grid[0] * k_grid[1] * k_grid[2]);
154 return result;
155 }
156
157 // -------------------------------------------
158
173 template <typename Mesh> auto integrate_ptr(auto const &f_kw, Mesh const &w_mesh, std::array<long, 3> const &k_grid, mpi::communicator comm = {}) {
174
175 int dim = detail::deduce_dim_from_expression(f_kw);
176 auto g_out = gf{w_mesh, {dim, dim}};
177 std::vector<typename Mesh::mesh_point_t> mesh_points(w_mesh.begin(), w_mesh.end());
178 auto ptr_result = integrate_ptr(f_kw, mesh_points, k_grid, comm);
179 // fill in the GF to return
180 for (auto &&[n, w] : itertools::enumerate(mpi::chunk(w_mesh, comm))) { g_out[w] = calc(w); }
181 return g_out;
182 }
183
184 // -------------------------------------------
196 auto integrate_adaptive(auto const &f_kw, adaptive_options const &opt) {
197
198 // use the first mesh value, evaluated, to determine the return type of the data
199 namespace ph = placeholders;
200 auto f_value = nda::make_regular(eval(f_kw, ph::kx = 0., ph::ky = 0., ph::kz = 0., ph::w = 0));
201 auto int_1d_adapt = utility::integrate_1d_adapt<decltype(f_value)>{opt.tolerance};
202
203 // OP : Beware the capture ! We need to move the expression.
204 return [expr_kw = std::move(f_kw), int_1d_adapt](auto om) { // NOLINT
205 std::pair<double, double> k_domain = {0, 1};
206 auto expr_k = eval(expr_kw, ph::w = om);
207
208 return utility::integrate(int_1d_adapt,
209 utility::integrate(int_1d_adapt, utility::integrate(int_1d_adapt, expr_k, ph::kx = k_domain), ph::ky = k_domain),
210 ph::kz = k_domain);
211 };
212 }
213
226 template <typename Mesh> auto integrate_adaptive(auto const &f_kw, Mesh const &w_mesh, adaptive_options const &opt) {
227
228 int dim = detail::deduce_dim_from_expression(f_kw);
229 auto g_out = gf{w_mesh, {dim, dim}};
230 // OMP/MPI parallel evaluation over frequencies
231 mpi::communicator comm = {};
232 auto calc = integrate_adaptive(f_kw, opt);
233 for (auto &&[n, w] : itertools::enumerate(mpi::chunk(w_mesh, comm))) { g_out[w] = calc(w); }
234 return g_out;
235 }
236
237 // -------------------------------------------
255 template <typename Mesh>
256 gf<Mesh, matrix_valued> integrate_bz(auto const &f_kw, Mesh const &w_mesh, bz_int_options const &opt, mpi::communicator comm = {}) {
257
258 namespace ph = placeholders;
259
260 // set up kgrid
261 auto k_grid = opt.k_grid;
262 auto kgrid_above_max = [&](auto &k_grid) {
263 for (auto ik : {0, 1, 2})
264 if (k_grid[ik] >= opt.k_grid_max[ik]) { return true; }
265 return false;
266 };
267
268 // REFACTOR this seems like it should go into a constructor for a bz_int_opt object
269 // note that the PTR will always run once as long as run_ptr = true
270 if (opt.tolerance <= 0) { throw std::runtime_error("Must provide a positive tolerance."); }
271 if (!opt.run_ptr and !opt.run_adaptive) {
272 throw std::runtime_error("Must choose at least one of run_ptr or run_adaptive for BZ integration to work.");
273 }
274 if (opt.run_ptr) { // check PTR options are sound
275 if (!std::all_of(k_grid.begin(), k_grid.end(), [&](int k) { return k > 0; })) { // check if values are positive
276 throw std::runtime_error("Cannot run integraton with k_grid <= 0. ");
277 }
278 // check that kgrid increment is meaningful
279 if (!std::all_of(opt.delta_k_grid.begin(), opt.delta_k_grid.end(), [&](int k) { return k >= 0; })) {
280 throw std::runtime_error("delta_k_grid cannot be negative.");
281 }
282 // delta_k_grid = 0 is reasonable only if k_grid >= k_grid_max, otherwise this will run doing nothing
283 if (std::all_of(opt.delta_k_grid.begin(), opt.delta_k_grid.end(), [&](int k) { return k == 0; }) and !kgrid_above_max(k_grid)) {
284 throw std::runtime_error("delta_k_grid can only be zero if k_grid >= k_grid_max.");
285 }
286 }
287
288 int dim = detail::deduce_dim_from_expression(f_kw);
289 auto g_out = gf{w_mesh, {dim, dim}};
290
291 // set up containers to check if ptr has converged for different points
292 std::vector<bool> ptr_converged(w_mesh.size(), false);
293 auto all_converged = [&]() { return std::ranges::all_of(ptr_converged, std::identity()); };
294
295 // set up initialize set of omega values to be run (all of them for first loop)
296 std::vector<typename Mesh::mesh_point_t> mesh_points(w_mesh.begin(), w_mesh.end());
297
298 // ------ Do the PTR -------
299 if (opt.run_ptr) {
300 do { // NOLINT (run loop at least once if PTR is chosen )
301
302 if (opt.verbose) {
303 int remaining_ptr = static_cast<int>(ptr_converged.size()) - std::reduce(ptr_converged.begin(), ptr_converged.end());
304 std::cout << "Points remaining unconverged: " << remaining_ptr << ", now running with k-grid " << k_grid[0] << " " << k_grid[1] << " "
305 << k_grid[2] << std::endl;
306 }
307
308 // update the list of omega values we need to cover
309 auto ptr_result = integrate_ptr(f_kw, mesh_points, k_grid, comm);
310
311 // check which ones are converged after this run
312 for (auto &&[n, w] : itertools::enumerate(mesh_points)) {
313 ptr_converged[w.data_index()] = (max_element(abs(ptr_result(n, nda::range::all, nda::range::all) - g_out[w])) < opt.tolerance);
314 g_out[w] = ptr_result(n, nda::range::all, nda::range::all);
315 }
316 // update list of unconverged frequencies to work on
317 // REFACTOR it's much nicer to use the below line if we later can
318 // mesh_points = mesh_points | std::views::filter([&](auto om) { return !ptr_converged[om.data_index()]; }) | std::ranges::to<std::vector>();
319 std::vector<typename Mesh::mesh_point_t> unconv_mesh_points;
320 for (auto w : mesh_points) {
321 if (!ptr_converged[w.data_index()]) unconv_mesh_points.emplace_back(w);
322 }
323 mesh_points = unconv_mesh_points;
324
325 // increment the grid for the next iteration
326 for (auto ik : {0, 1, 2}) k_grid[ik] += opt.delta_k_grid[ik];
327
328 } while (!all_converged() and !kgrid_above_max(k_grid));
329 }
330
331 // ------- Execute adaptive algo for the frequencies PTR could not do ----------
332 if (opt.run_adaptive) {
333 adaptive_options adaptive_opt = {.tolerance = opt.tolerance};
334 if (opt.verbose) std::cout << "Running adaptive integration on remaining points." << std::endl;
335 auto calc = integrate_adaptive(f_kw, adaptive_opt);
336 // adaptive evaluation at each frequency is MPI parallel;
337 // possibly could be done better but this is ok for now
338 for (auto &&[n, w] : itertools::enumerate(mpi::chunk(g_out.mesh(), comm))) {
339 if (not ptr_converged[n]) g_out[w] = calc(w);
340 }
341 }
342 return g_out;
343 }
344
346
347} // namespace triqs::experimental::lattice
Adaptive one-dimensional integrator based on a 13-point Gauss-Kronrod-Lobatto rule.
Definition adaptive.hpp:31
The owning Green's function container.
Definition gf.hpp:194
Umbrella header for the Green's function library.
nda::array< dcomplex, 3 > integrate_ptr(auto const &f_kw, std::vector< T > const &omega_values, std::array< long, 3 > const &k_grid, mpi::communicator comm)
Integrate an expression over the Brillouin zone on a fixed k-grid (PTR) for a list of frequencies,...
auto integrate_adaptive(auto const &f_kw, adaptive_options const &opt)
Build a callable that adaptively integrates an expression over the Brillouin zone for a given frequen...
gf< Mesh, matrix_valued > integrate_bz(auto const &f_kw, Mesh const &w_mesh, bz_int_options const &opt, mpi::communicator comm={})
Integrate an expression over the Brillouin zone for all frequencies, combining PTR and adaptive integ...
auto integrate(auto const &integrator1d, auto expr_to_integrate, nda::clef::pair< I, D > const &p)
Integrate a CLEF expression over one of its placeholders using an arbitrary one-dimensional integrato...
CLEF placeholders for the momentum and frequency arguments of integrable expressions.
constexpr nda::clef::placeholder< 0 > kx
Placeholder for the first momentum component .
constexpr nda::clef::placeholder< 1 > ky
Placeholder for the second momentum component .
constexpr nda::clef::placeholder< 2 > kz
Placeholder for the third momentum component .
constexpr nda::clef::placeholder< 3 > w
Placeholder for the frequency .
Options controlling the adaptive Brillouin-zone integration.
double tolerance
Target absolute error of the adaptive integration.
Options controlling the combined PTR and adaptive Brillouin-zone integration.
std::array< long, 3 > k_grid_max
Maximum number of k-points along each direction.
bool run_adaptive
Whether to run adaptive integration on the remaining points.
std::array< long, 3 > delta_k_grid
Increment of the k-grid size at each refinement step.
std::array< long, 3 > k_grid
Initial number of PTR k-points along each direction.
bool verbose
Whether to print the convergence progress.
double tolerance
Absolute tolerance of the integrated quantity.
bool run_ptr
Whether to run PTR integration before the adaptive step.