TRIQS/TRIQS 4.0.0
Researching Interacting Quantum Systems
Loading...
Searching...
No Matches
fourier_polynomial.hpp
1#pragma once
2
4
5#include <h5/h5.hpp>
6#include <itertools/itertools.hpp>
7#include <mpi/mpi.hpp>
8#include <nda/h5.hpp>
9#include <nda/mpi.hpp>
10#include <nda/nda.hpp>
11
12#include <algorithm>
13#include <array>
14#include <cmath>
15#include <complex>
16#include <iterator>
17#include <ranges>
18#include <string>
19#include <tuple>
20#include <type_traits>
21#include <utility>
22#include <vector>
23
24namespace triqs::experimental::lattice {
25
26 using nda::dcomplex;
27
32
33 template <int coeff_dim, int kdim> class fourier_polynomial;
34
35 // ---------------------------------------------------------------------------
36 // Free Fourier-evaluation kernels.
37 //
38 // These take the packed R-matrix of lattice vectors (shape [nR, kdim]) and a
39 // packed coefficient array (shape [nR, coeff_shape...]) and evaluate
40 //
41 // f(k) = sum_R coeff(R) * exp(2 pi i k . R)
42 //
43 // at a single k-point, a matrix of k-points, or any contiguous range of
44 // k-like objects (anything with operator[](int)). Both fourier_polynomial's
45 // member operator() and triqs::experimental::gfs::fourier_eval /
46 // make_gf_from_fourier call them.
47 // ---------------------------------------------------------------------------
48
57 template <int kdim> nda::matrix<double> make_R_mat(auto const &r_list) {
58 nda::matrix<double> rm(r_list.size(), kdim);
59 for (long i = 0; i < static_cast<long>(r_list.size()); ++i)
60 for (int d = 0; d < kdim; ++d) rm(i, d) = static_cast<double>(r_list[i][d]);
61 return rm;
62 }
63
78 template <int coeff_dim, int kdim, typename CoeffArr>
79 auto fourier_eval(nda::matrix_const_view<double> R_mat, CoeffArr const &coeff_arr, std::array<double, kdim> const &k) {
80 auto c_shape = coeff_arr.shape();
81
82 // coeff_dim == 0 (scalar target): accumulate into a plain dcomplex since
83 // nda::array<T, 0> is not a valid container type. For matrix-valued
84 // targets we preserve the nda::matrix type so matmul/linalg work directly.
85 auto res = [&]() {
86 if constexpr (coeff_dim == 0)
87 return dcomplex{0};
88 else if constexpr (coeff_dim == 2)
89 return nda::matrix<dcomplex>::zeros(nda::stdutil::front_pop(c_shape));
90 else
91 return nda::array<dcomplex, coeff_dim>::zeros(nda::stdutil::front_pop(c_shape));
92 }();
93
94 long nR = R_mat.shape(0);
95 for (long idx = 0; idx < nR; ++idx) {
96 double kR = 0;
97 for (int d = 0; d < kdim; ++d) kR += k[d] * R_mat(idx, d);
98 res += std::exp(2i * M_PI * kR) * coeff_arr(idx, nda::ellipsis{});
99 }
100 return res;
101 }
102
116 template <int coeff_dim, typename CoeffArr>
117 nda::array<dcomplex, coeff_dim + 1> fourier_eval(nda::matrix_const_view<double> R_mat, CoeffArr const &coeff_arr,
118 nda::array_const_view<double, 2> k_list) {
119 auto ks = nda::matrix_const_view<double>(k_list);
120 long nk = ks.shape(0);
121
122 // set up the phases
123 nda::array<double, 2> kdotR(nk, R_mat.shape(0));
124 nda::blas::gemm(1., ks, transpose(R_mat), 0, kdotR);
125 nda::matrix<dcomplex> phases = nda::exp(2i * M_PI * kdotR);
126
127 auto c_shape = coeff_arr.shape();
128
129 // flat product of the trailing target dims (1 if the target is scalar)
130 long trailing = 1;
131 for (int i = 1; i < coeff_dim + 1; ++i) trailing *= c_shape[i];
132
133 auto coeff_arr_view = nda::reshape(coeff_arr, std::array{c_shape[0], trailing});
134
135 auto result_shape = c_shape;
136 result_shape[0] = nk;
137 auto result = nda::array<dcomplex, coeff_dim + 1>(result_shape);
138 auto result_mat_view = nda::reshape(result, std::array{nk, trailing});
139
140 nda::blas::gemm(1, phases, coeff_arr_view, 0, result_mat_view);
141 return result;
142 }
143
158 template <int coeff_dim, int kdim, typename CoeffArr, typename V>
159 requires(std::ranges::contiguous_range<V>)
160 nda::array<dcomplex, coeff_dim + 1> fourier_eval(nda::matrix_const_view<double> R_mat, CoeffArr const &coeff_arr, V const &k_iterator) {
161 auto kvecs = nda::matrix<double>(k_iterator.size(), kdim);
162 for (auto [ik, k] : itertools::enumerate(k_iterator)) {
163 for (auto idim : nda::range(kdim)) { kvecs(ik, idim) = k[idim]; }
164 }
165 return fourier_eval<coeff_dim>(R_mat, coeff_arr, nda::array_const_view<double, 2>(kvecs));
166 }
167
168 // ---------------------------------------------------------------------------
169 // fourier_polynomial: owning container for R-vectors + Fourier coefficients,
170 // plus a placeholder-aware operator() for clef lazy evaluation.
171 // ---------------------------------------------------------------------------
172
188 template <int coeff_dim, int kdim> class fourier_polynomial {
189
190 protected:
191 std::vector<std::array<long, kdim>> R_list;
192 nda::matrix<double> R_mat;
193 nda::array<dcomplex, coeff_dim + 1> coeff_arr;
194
196 void h5_write_impl(h5::group g, std::string const &name, char const *format) const {
197 auto gr = g.create_group(name);
198 h5::write_hdf5_format_as_string(gr, format); // NOLINT
199 h5::write(gr, "R_list", R_list);
200 h5::write(gr, "coeff_arr", coeff_arr);
201 }
202
204 void h5_read_impl(h5::group g, std::string const &name, char const *exp_format) {
205 auto gr = g.open_group(name);
206 h5::assert_hdf5_format_as_string(gr, exp_format, true); // NOLINT
207 h5::read(gr, "R_list", R_list);
208 h5::read(gr, "coeff_arr", coeff_arr);
209 TRIQS_ASSERT(R_list.size() == coeff_arr.shape(0));
211 }
212
213 public:
221 fourier_polynomial &operator=(fourier_polynomial const &) = default;
223 fourier_polynomial &operator=(fourier_polynomial &&) noexcept = default;
224
231 fourier_polynomial(std::vector<std::array<long, kdim>> R_list_, std::vector<nda::array<dcomplex, coeff_dim>> const &coeff_list_)
232 : R_list(std::move(R_list_)), R_mat(make_R_mat<kdim>(R_list)) {
233 TRIQS_ASSERT(!coeff_list_.empty());
234 TRIQS_ASSERT(R_list.size() == coeff_list_.size());
235 coeff_arr.resize(nda::stdutil::front_append(coeff_list_[0].shape(), coeff_list_.size()));
236 for (long i = 0; i < static_cast<long>(coeff_list_.size()); ++i) coeff_arr(i, nda::ellipsis{}) = coeff_list_[i];
237 }
238
245 fourier_polynomial(std::vector<std::array<long, kdim>> R_list_, nda::array<dcomplex, coeff_dim + 1> coeff_arr_)
246 : R_list(std::move(R_list_)), R_mat(make_R_mat<kdim>(R_list)), coeff_arr(std::move(coeff_arr_)) {
247 TRIQS_ASSERT(R_list.size() == coeff_arr.shape(0));
248 }
249
260 friend void mpi_broadcast(fourier_polynomial &x, mpi::communicator c = {}, int root = 0) {
261 mpi::broadcast(x.R_list, c, root);
262 mpi::broadcast(x.coeff_arr, c, root);
264 }
265
271 [[nodiscard]] static std::string hdf5_format() { return "fourier_polynomial"; }
272
280 friend void h5_write(h5::group g, std::string const &name, fourier_polynomial const &x) { x.h5_write_impl(g, name, "fourier_polynomial"); }
281
289 friend void h5_read(h5::group g, std::string const &name, fourier_polynomial &x) { x.h5_read_impl(g, name, "fourier_polynomial"); }
290
296 [[nodiscard]] C2PY_IGNORE auto const &get_R_list() const { return R_list; }
297
303 [[nodiscard]] C2PY_IGNORE auto get_coeff_arr() { return coeff_arr(); }
304
310 [[nodiscard]] C2PY_IGNORE auto get_coeff_arr() const { return coeff_arr(); }
311
317 [[nodiscard]] C2PY_IGNORE auto const &get_R_mat() const { return R_mat; }
318
324 [[nodiscard]] long n_R() const { return static_cast<long>(R_list.size()); }
325
332 C2PY_IGNORE auto operator[](std::array<long, kdim> R) { return coeff_arr(get_R_idx(R), nda::ellipsis{}); }
333
340 C2PY_IGNORE auto operator[](std::array<long, kdim> R) const { return coeff_arr(get_R_idx(R), nda::ellipsis{}); }
341
348 C2PY_IGNORE auto operator[](long i) { return coeff_arr(i, nda::ellipsis{}); }
349
356 C2PY_IGNORE auto operator[](long i) const { return coeff_arr(i, nda::ellipsis{}); }
357
366 long get_R_idx(std::array<long, kdim> R) const {
367 auto it = std::ranges::find(R_list, R);
368 if (it == R_list.end()) { TRIQS_RUNTIME_ERROR << "Could not locate R in the Wannier Hamiltonian.\n"; }
369 return std::distance(R_list.begin(), it);
370 }
371
386 template <typename... T>
387 requires((std::is_same_v<T, double> or nda::clef::is_lazy<T>) and ...) // double OR placeholder
388 auto operator()(T... ks) const {
389
390 static_assert(sizeof...(T) == kdim, "Incorrect number of arguments"); // # arguments is same as dimension
391 constexpr auto ph_number = ((nda::clef::is_lazy<T> + ...)); // How many placeholders in ks
392
393 // Called with 0 placeholder
394 if constexpr (ph_number == 0) return operator()(std::array{ks...});
395 // Called with 1 placeholder, not valid for kdim = 1
396 else if constexpr ((ph_number == 1) and (kdim != 1)) {
397 // find the position of the placeholder
398 constexpr auto is_ph = std::array{(nda::clef::is_lazy<T> ? 1 : 0)...}; // 0 or 1 if it is a placeholder
399 constexpr int ph_position = [](auto &&v) {
400 for (int i = 0; i < v.size(); ++i)
401 if (v[i] == 1) return i;
402 return -1; // to silence compiler warning
403 }(is_ph); // immediately calling the lambda. executed at compile time
404
405 return nda::clef::make_expr_call(partial_eval<ph_position>(ks...), // auto{k...[ph_position]}); // C++26
406 auto{std::get<ph_position>(std::tie(ks...))});
407 }
408 // Called with > 1 placeholders in kdim > 1 or 1 placeholder in kdim == 1
409 else
410 return nda::clef::make_expr_call(fourier_polynomial{*this}, auto{ks}...);
411 }
412
413 private:
417 template <int placeholder_pos>
418 fourier_polynomial<coeff_dim, 1> partial_eval(auto const &...ks) const
419 requires((kdim == 3) or (kdim == 2))
420 {
421
422 auto [it1, it2] = std::ranges::minmax_element(R_list, {}, [](auto const &r) { return r[placeholder_pos]; });
423
424 auto Rz_min = (*it1)[placeholder_pos];
425 auto Rz_max = (*it2)[placeholder_pos];
426
427 long n_new = Rz_max - Rz_min + 1;
428 auto new_Rs = std::vector<std::array<long, 1>>(n_new);
429 auto coeff_shape = nda::stdutil::front_pop(coeff_arr.shape());
430 auto new_coeff_arr = nda::array<dcomplex, coeff_dim + 1>::zeros(nda::stdutil::front_append(coeff_shape, n_new));
431
432 constexpr int p1 = (placeholder_pos + 1) % kdim;
433 constexpr int p2 = (placeholder_pos + 2) % kdim;
434 auto kst = std::tie(ks...);
435
436 for (long idx = 0; idx < n_R(); ++idx) {
437 auto Rz = R_list[idx][placeholder_pos];
438 long new_idx = Rz - Rz_min;
439 new_Rs[new_idx] = {Rz};
440
441 double kR = std::get<p1>(kst) * R_list[idx][p1];
442 if constexpr (kdim == 3) kR += std::get<p2>(kst) * R_list[idx][p2];
443 new_coeff_arr(new_idx, nda::ellipsis{}) += coeff_arr(idx, nda::ellipsis{}) * std::exp(2i * M_PI * kR);
444 }
445
446 return {std::move(new_Rs), std::move(new_coeff_arr)};
447 }
448
449 // -----------------------------
450
451 public:
458 nda::array<dcomplex, coeff_dim + 1> operator()(nda::array_const_view<double, 2> k_list) const {
459 return fourier_eval<coeff_dim>(R_mat, coeff_arr, k_list);
460 }
461
468 nda::array<dcomplex, coeff_dim + 1> operator()(std::ranges::contiguous_range auto const &k_iterator) const {
469 return fourier_eval<coeff_dim, kdim>(R_mat, coeff_arr, k_iterator);
470 }
471
478 auto operator()(std::array<double, kdim> ks) const { return fourier_eval<coeff_dim, kdim>(R_mat, coeff_arr, ks); }
479 };
480
482
483} // namespace triqs::experimental::lattice
484
486// this allows the deep partial evaluation mechanism to speed up calculations
487template <int coeff_dim, int kdim>
488inline constexpr bool nda::clef::supports_partial_eval_of_calls<triqs::experimental::lattice::fourier_polynomial<coeff_dim, kdim>> = true;
const_view_type operator()() const
Make a const view of *this.
Owning container for a Fourier-series representation of a lattice function.
auto operator[](std::array< long, kdim > R) const
Access the coefficient associated with a given R-vector (const overload).
void h5_write_impl(h5::group g, std::string const &name, char const *format) const
HDF5 write helper for derived classes: writes R_list and coeff_arr under the given format tag.
nda::array< dcomplex, coeff_dim+1 > operator()(nda::array_const_view< double, 2 > k_list) const
Evaluate the Fourier polynomial at a batch of k-points given as a matrix.
auto get_coeff_arr() const
Get the packed coefficient array (const overload).
friend void mpi_broadcast(fourier_polynomial &x, mpi::communicator c={}, int root=0)
Broadcast a Fourier polynomial over an MPI communicator.
fourier_polynomial(fourier_polynomial &&) noexcept=default
Move constructor.
long get_R_idx(std::array< long, kdim > R) const
Get the storage index of a given R-vector.
auto operator[](long i)
Access the coefficient at a given storage index.
auto operator()(std::array< double, kdim > ks) const
Evaluate the Fourier polynomial at a single k-point.
nda::matrix< double > R_mat
Lattice vectors as doubles, of shape [nR, kdim], for BLAS.
nda::array< dcomplex, coeff_dim+1 > operator()(std::ranges::contiguous_range auto const &k_iterator) const
Evaluate the Fourier polynomial at a contiguous range of k-points.
auto operator[](std::array< long, kdim > R)
Access the coefficient associated with a given R-vector.
nda::array< dcomplex, coeff_dim+1 > coeff_arr
Packed Fourier coefficients, of shape [nR, coeff_shape...].
friend void h5_read(h5::group g, std::string const &name, fourier_polynomial &x)
Read a Fourier polynomial from HDF5.
fourier_polynomial(fourier_polynomial const &)=default
Copy constructor.
static std::string hdf5_format()
Get the HDF5 format tag.
long n_R() const
Get the number of R-vectors.
std::vector< std::array< long, kdim > > R_list
Lattice vectors as integer arrays.
auto const & get_R_list() const
Get the list of real-space lattice vectors.
fourier_polynomial()=default
Default constructor: construct an empty Fourier polynomial.
friend void h5_write(h5::group g, std::string const &name, fourier_polynomial const &x)
Write a Fourier polynomial to HDF5.
auto const & get_R_mat() const
Get the packed matrix of lattice vectors.
auto get_coeff_arr()
Get the packed coefficient array.
void h5_read_impl(h5::group g, std::string const &name, char const *exp_format)
HDF5 read helper for derived classes: reads R_list and coeff_arr in place; rebuilds R_mat.
auto operator[](long i) const
Access the coefficient at a given storage index (const overload).
fourier_polynomial(std::vector< std::array< long, kdim > > R_list_, nda::array< dcomplex, coeff_dim+1 > coeff_arr_)
Construct from a list of R-vectors and a packed coefficient array.
TRIQS exception hierarchy and related macros.
auto fourier_eval(nda::matrix_const_view< double > R_mat, CoeffArr const &coeff_arr, std::array< double, kdim > const &k)
Evaluate a Fourier series at a single k-point.
nda::matrix< double > make_R_mat(auto const &r_list)
Build the packed matrix of lattice vectors from a sequence of R-vectors.
gf< M, matrix_valued > transpose(gf_view< M, matrix_valued > g)
Transpose the target matrix of a matrix-valued Green's function, returning a new Green's function.
#define TRIQS_RUNTIME_ERROR
Throw a triqs::runtime_error with the current source location.
#define TRIQS_ASSERT(X)
Throw a triqs::runtime_error if the boolean expression X evaluates to false.