TRIQS/nrgljubljana_interface 4.0.0
A TRIQS application
Loading...
Searching...
No Matches
hilbert_transform.hpp
1// Rok Zitko, Nils Wentzell, Dec 2019
2
3#include <triqs/gfs.hpp>
4#include <triqs/mesh.hpp>
5#include <itertools/itertools.hpp>
6
7#include <gsl/gsl_errno.h>
8#include <gsl/gsl_integration.h>
9#include <gsl/gsl_spline.h>
10
11#include <cmath>
12
13namespace triqs::gfs {
14
15 // Unwrap a lambda expression and evaluate it at x
16 // https://martin-ueding.de/articles/cpp-lambda-into-gsl/index.html
17 inline double unwrap(double x, void *p) {
18 auto fp = static_cast<std::function<double(double)> *>(p);
19 return (*fp)(x);
20 }
21
22 // Wrap around GSL integration routines
23 class integrator {
24 private:
25 size_t limit; // size of workspace
26 bool exit_on_error; // if true, integration error will trigger a hard error
27 gsl_integration_workspace *work = nullptr; // work space
28 gsl_function F; // GSL function struct for evaluation of the integrand
29 public:
30 void initF() noexcept {
31 F.function = &unwrap; // the actual function (lambda expression) will be provided as an argument to operator()
32 F.params = nullptr;
33 }
34 integrator(size_t _limit = 1000, bool _exit_on_error = false) : limit{_limit}, exit_on_error{_exit_on_error} {
35 work = gsl_integration_workspace_alloc(limit);
36 initF();
37 }
38 integrator(const integrator &X) : limit{X.limit}, exit_on_error{X.exit_on_error} {
39 // keep the same workspace
40 initF();
41 }
42 integrator(integrator &&X) : limit{X.limit}, exit_on_error{X.exit_on_error} {
43 work = X.work; // steal workspace
44 X.work = nullptr;
45 initF();
46 }
47 integrator& operator=(const integrator &X) {
48 if (this==&X) return *this;
49 limit = X.limit;
50 exit_on_error = X.exit_on_error;
51 // keep the same workspace
52 initF();
53 return *this;
54 }
55 integrator& operator=(integrator &&X) {
56 if (this==&X) return *this;
57 limit = X.limit;
58 exit_on_error = X.exit_on_error;
59 work = X.work; // steal workspace
60 X.work = nullptr;
61 initF();
62 return *this;
63 }
64 ~integrator() {
65 if (work) { gsl_integration_workspace_free(work); }
66 }
67
77 double operator() (std::function<double(double)> f, double a, double b, double epsabs = 1e-14, double epsrel = 1e-10) {
78 F.params = &f;
79 double result, error;
80 int status = gsl_integration_qag(&F, a, b, epsabs, epsrel, limit, GSL_INTEG_GAUSS15, work, &result, &error);
81 if (status && abs(result) > epsabs && exit_on_error) TRIQS_RUNTIME_ERROR << "qag error: " << status << " -- " << gsl_strerror(status);
82 return result;
83 }
84 };
85
86 // Wrap around GSL interpolation routines
87 class interpolator {
88 private:
89 size_t len; // number of data points
90 std::vector<double> X, Y; // X and Y tables
91 double Xmin, Xmax; // boundary points
92 double oob_value; // out-of-boundary value
93 gsl_interp_accel *acc = nullptr; // workspace
94 gsl_spline *spline = nullptr; // spline data
95 public:
96 interpolator(std::vector<double> &_X, std::vector<double> &_Y, double _oob_value = 0.0) : X{_X}, Y{_Y}, oob_value{_oob_value} {
97 EXPECTS(std::is_sorted(X.begin(), X.end()));
98 EXPECTS(X.size() == Y.size());
99 acc = gsl_interp_accel_alloc();
100 len = X.size();
101 spline = gsl_spline_alloc(gsl_interp_cspline, len);
102 gsl_spline_init(spline, &X[0], &Y[0], len);
103 Xmin = X[0];
104 Xmax = X[len-1];
105 }
106 interpolator(const interpolator &I) : len{I.len}, X{I.X}, Y{I.Y}, Xmin{I.Xmin}, Xmax{I.Xmax}, oob_value{I.oob_value} {
107 // keep the same accelerator workspace
108 if (spline) { gsl_spline_free(spline); } // we need new spline data object
109 spline = gsl_spline_alloc(gsl_interp_cspline, len);
110 gsl_spline_init(spline, &X[0], &Y[0], len);
111 }
112 interpolator(interpolator &&I) : len{I.len}, X{I.X}, Y{I.Y}, Xmin{I.Xmin}, Xmax{I.Xmax}, oob_value{I.oob_value} {
113 acc = I.acc; // steal workspace
114 I.acc = nullptr;
115 spline = I.spline; // steal spline data
116 I.spline = nullptr;
117 }
118 interpolator& operator=(const interpolator &I) {
119 if (this==&I) return *this;
120 len = I.len;
121 X = I.X;
122 Y = I.Y;
123 Xmin = I.Xmin;
124 Xmax = I.Xmax;
125 oob_value = I.oob_value;
126 // keep the same accelerator workspace
127 if (spline) { gsl_spline_free(spline); } // we need new spline data object
128 spline = gsl_spline_alloc(gsl_interp_cspline, len);
129 gsl_spline_init(spline, &X[0], &Y[0], len);
130 return *this;
131 }
132 interpolator& operator=(interpolator &&I) {
133 if (this==&I) return *this;
134 len = I.len;
135 X = std::move(I.X);
136 Y = std::move(I.Y);
137 Xmin = I.Xmin;
138 Xmax = I.Xmax;
139 oob_value = I.oob_value;
140 acc = I.acc; // steal workspace
141 I.acc = nullptr;
142 spline = I.spline; // steal spline data
143 I.spline = nullptr;
144 return *this;
145 }
146 ~interpolator() {
147 if (spline) { gsl_spline_free(spline); }
148 if (acc) { gsl_interp_accel_free(acc); }
149 }
150 double operator() (double x) { return (Xmin <= x && x <= Xmax ? gsl_spline_eval(spline, x, acc) : oob_value); }
151 };
152
153 // Square of x
154 inline double sqr(double x) { return x*x; }
155
156 // Result of Integrate[(-y/(y^2 + (x - omega)^2)), {omega, -B, B}] (atg -> imQ).
157 inline double imQ(double x, double y, double B) { return atan((-B + x) / y) - atan((B + x) / y); }
158
159 // Result of Integrate[((x - omega)/(y^2 + (x - omega)^2)), {omega, -B, B}] (logs -> reQ).
160 inline double reQ(double x, double y, double B) { return (-log(sqr(B - x) + sqr(y)) + log(sqr(B + x) + sqr(y))) / 2.0; }
161
162 // Calculate the (half)bandwidth, i.e., the size B of the enclosing interval [-B:B].
163 inline double bandwidth(std::vector<double> X) {
164 EXPECTS(std::is_sorted(X.begin(), X.end()));
165 size_t len = X.size();
166 double Xmin = X[0];
167 double Xmax = X[len-1];
168 return std::max(abs(Xmin), abs(Xmax));
169 }
170
182 template <typename G> dcomplex hilbert_transform(G const &Ain, dcomplex z, double lim_direct = 1e-3) requires(is_gf_v<G>) {
183 static_assert(std::is_same_v<typename G::target_t, scalar_valued>,
184 "Hilbert transform only implemented for (complex) scalar-valued spectral functions");
185 static_assert(std::is_same_v<typename G::mesh_t, mesh::refreq> or std::is_same_v<typename G::mesh_t, mesh::refreq_pts>
186 or std::is_same_v<typename G::mesh_t, mesh::refreq_log>,
187 "Hilbert transform only implemented for refreq, refreq_pts, and refreq_log meshes");
188 // Copy the input data for GSL interpolation routines.
189 using DVEC = std::vector<double>;
190 DVEC Xpts, Rpts, Ipts;
191// TODO: make this work:
192// auto const & w_mesh = Ain.mesh();
193// auto Xpts = std::vector<double>(w_mesh.size());
194// std::copy(w_mesh.begin(), w_mesh.end(), Xpts.begin());
195// auto Rpts = make_regular(real(Ain.data()));
196// auto Ipts = make_regular(imag(Ain.data()));
197// Workaround:
198 for (const auto &w : Ain.mesh()) {
199 double x = w;
200 double r = real(Ain[w]);
201 double i = imag(Ain[w]);
202 Xpts.push_back(x);
203 Rpts.push_back(r);
204 Ipts.push_back(i);
205 }
206 // Initialize GSL and set up the interpolation
207 gsl_set_error_handler_off();
208 interpolator rhor(Xpts, Rpts);
209 interpolator rhoi(Xpts, Ipts);
210 integrator integr;
211 double x = real(z);
212 double y = imag(z);
213 double B = bandwidth(Xpts);
214 // Low-level Hilbert-transform routines. calcA routine handles the case with removed singularity and
215 // perform the integration after a change of variables. calcB routine directly evaluates the defining
216 // integral of the Hilbert transform. Real and imaginary parts are determined in separate steps.
217 auto calcA = [&integr, x, y, B](auto f3p, auto f3m, auto d) -> double {
218 const double W1 = (x - B) / abs(y); // Rescaled integration limits. Only the absolute value of y matters here.
219 const double W2 = (B + x) / abs(y);
220 assert(W2 >= W1);
221 // Determine the integration limits depending on the values of (x,y).
222 double lim1down = 1.0, lim1up = -1.0, lim2down = 1.0, lim2up = -1.0;
223 bool inside;
224 if (W1 < 0 && W2 > 0) { // x within the band
225 const double ln1016 = -36.8; // \approx log(10^-16)
226 lim1down = ln1016;
227 lim1up = log(-W1);
228 lim2down = ln1016;
229 lim2up = log(W2);
230 inside = true;
231 } else
232 if (W1 > 0 && W2 > 0) { // x above the band
233 lim2down = log(W1);
234 lim2up = log(W2);
235 inside = false;
236 } else
237 if (W1 < 0 && W2 < 0) { // x below the band
238 lim1down = log(-W2);
239 lim1up = log(-W1);
240 inside = false;
241 } else { // special case: boundary points
242 inside = true;
243 }
244 auto result1 = (lim1down < lim1up ? integr(f3p, lim1down, lim1up) : 0.0);
245 auto result2 = (lim2down < lim2up ? integr(f3m, lim2down, lim2up) : 0.0);
246 auto result3 = (inside ? d : 0.0);
247 return result1 + result2 + result3;
248 };
249 auto calcB = [&integr, B](auto f0) -> double { return integr(f0, -B, B); }; // direct integration
250 auto calc = [y, lim_direct, calcA, calcB](auto f3p, auto f3m, auto d, auto f0){
251 return (abs(y) < lim_direct ? calcA(f3p, f3m, d) : calcB(f0));
252 };
253
254 // Re part of rho(omega)/(z-omega)
255 auto ref0 = [x,y,&rhor,&rhoi](double omega) -> double { return ( rhor(omega)*(x-omega) + rhoi(omega)*y )/(sqr(y)+sqr(x-omega)); };
256
257 // Im part of rho(omega)/(z-omega)
258 auto imf0 = [x,y,&rhor,&rhoi](double omega) -> double { return (rhor(omega)*(-y) + rhoi(omega)*(x-omega) )/(sqr(y)+sqr(x-omega)); };
259
260 // Re part of rho(omega)/(z-omega) with the singularity subtracted out.
261 auto ref1 = [x,y,&rhor,&rhoi](double omega) -> double { return ( (rhor(omega)-rhor(x))*(x-omega) + (rhoi(omega)-rhoi(x))*(y) )/(sqr(y)+sqr(x-omega)); };
262 auto ref2 = [x,y,ref1](double W) -> double { return abs(y)*ref1(abs(y)*W+x); };
263 auto ref3p = [ref2](double r) -> double { return ref2(exp(r)) * exp(r); };
264 auto ref3m = [ref2](double r) -> double { return ref2(-exp(r)) * exp(r); };
265 auto red = rhor(x) * reQ(x,y,B) - rhoi(x) * imQ(x,y,B);
266
267 // Im part of rho(omega)/(z-omega) with the singularity subtracted out.
268 auto imf1 = [x,y,&rhor,&rhoi](double omega) -> double { return ( (rhor(omega)-rhor(x))*(-y) + (rhoi(omega)-rhoi(x))*(x-omega) )/(sqr(y)+sqr(x-omega)); };
269 auto imf2 = [x,y,imf1](double W) -> double { return abs(y)*imf1(abs(y)*W+x); };
270 auto imf3p = [imf2](double r) -> double { return imf2(exp(r)) * exp(r); };
271 auto imf3m = [imf2](double r) -> double { return imf2(-exp(r)) * exp(r); };
272 auto imd = rhor(x) * imQ(x,y,B) + rhoi(x) * reQ(x,y,B);
273
274 return dcomplex{calc(ref3p, ref3m, red, ref0), calc(imf3p, imf3m, imd, imf0)};
275 }
276
286 template <typename G, typename M>
287 typename G::regular_type hilbert_transform(G const &Ain, M const &mesh, double eps = 1e-16)
288 requires(is_gf_v<G> and mesh::Mesh<M>) {
289 auto gout = gf<M, typename G::target_t>{mesh, Ain.target_shape()};
290 for (const auto mp : mesh) gout[mp] = hilbert_transform(Ain, dcomplex{mp,eps});
291 return gout;
292 }
293
303 template <typename G>
304 typename G::regular_type hilbert_transform(G const &Ain, G const &gin) requires(is_gf_v<G>) {
305 auto gout = gin;
306 for (const auto &mp : gin.mesh()) gout[mp] = hilbert_transform(Ain, gin[mp]);
307 return gout;
308 }
309
318 template <typename G> matrix<dcomplex> hilbert_transform_elementwise(G const &Ain, dcomplex z) requires(is_gf_v<G>) {
319 static_assert(std::is_same_v<typename G::target_t, matrix_valued>,
320 "Hilbert transform only implemented for matrix-valued spectral functions");
321 long size1 = Ain.target_shape()[0];
322 long size2 = Ain.target_shape()[1];
323 auto mat = matrix<dcomplex>(size1, size2);
324 for (auto [i, j] : itertools::product_range(size1, size2)) {
325 auto gtemp = gf{Ain.mesh()};
326 for (const auto &mp : Ain.mesh()) gtemp[mp] = Ain[mp](i,j);
327 mat(i,j) = hilbert_transform(gtemp, z);
328 }
329 return mat;
330 }
331
339 template <typename BG> auto hilbert_transform(BG const &bAin, dcomplex z) requires(is_block_gf_v<BG>) {
340 using G = typename BG::g_t;
341 auto l = [z](G const &Ain) { return hilbert_transform<G>(Ain, z); };
342 return map_block_gf(l, bAin);
343 }
344
353 template <typename BG, typename M>
354 auto hilbert_transform(BG const &bAin, M const &mesh, double eps = 1e-16) requires(is_block_gf_v<BG> and mesh::Mesh<M>) {
355 using G = typename BG::g_t;
356 auto l = [&mesh, eps](G const &Ain) { return hilbert_transform<G, M>(Ain, mesh, eps); };
357 return map_block_gf(l, bAin);
358 }
359
360} // namespace triqs::gfs