-
Notifications
You must be signed in to change notification settings - Fork 119
Expand file tree
/
Copy pathHankel.cpp
More file actions
257 lines (234 loc) · 9.59 KB
/
Hankel.cpp
File metadata and controls
257 lines (234 loc) · 9.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/* -*- c++ -*-
* Copyright (c) 2012-2026 by the GalSim developers team on GitHub
* https://github.com/GalSim-developers
*
* This file is part of GalSim: The modular galaxy image simulation toolkit.
* https://github.com/GalSim-developers/GalSim
*
* GalSim is free software: redistribution and use in source and binary forms,
* with or without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions, and the disclaimer given in the accompanying LICENSE
* file.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions, and the disclaimer given in the documentation
* and/or other materials provided with the distribution.
*/
//#define DEBUGLOGGING
#include <cmath>
#include <vector>
#include <map>
#include <functional>
#include "integ/Int.h"
#include "math/Hankel.h"
#include "math/Bessel.h"
#include "Std.h"
namespace galsim {
namespace math {
// Integrand class for the Hankel transform
class Integrand : public std::function<double(double)>
{
public:
Integrand(const std::function<double(double)> f, double k, double nu) :
_f(f), _k(k), _nu(nu)
{
xdbg<<"Make Integrand: "<<k<<std::endl;
}
double operator()(double r) const
{
xdbg<<"Call integrand: "<<r<<std::endl;
return r*_f(r) * math::cyl_bessel_j(_nu,_k*r);
}
private:
const std::function<double(double)> _f;
double _k;
double _nu;
};
// This is the straightforward GKP method for doing the Hankel integral.
double hankel_gkp(const std::function<double(double)> f, double k, double nu, double rmax,
double relerr, double abserr, int nzeros)
{
xdbg<<"Start hankel: "<<k<<" "<<rmax<<std::endl;
Integrand I(f, k, nu);
#ifdef DEBUGLOGGING
std::ostream* integ_dbgout = verbose_level >= 3 ? &Debugger::instance().get_dbgout() : 0;
integ::IntRegion<double> reg(0, rmax, integ_dbgout);
#else
integ::IntRegion<double> reg(0, rmax);
#endif
// Add explicit splits at first several roots of J0.
// This tends to make the integral more accurate.
for (int s=1; s<=nzeros; ++s) {
double root = math::getBesselRoot(nu,s);
if (root > k * rmax) break;
reg.addSplit(root/k);
}
return integ::int1d(I, reg, relerr, abserr);
}
class HankelIntegrator
{
public:
// Helper class that can perform a Hankel integral for a specific choice of h
// using the method of Ogata (2005):
// http://www.kurims.kyoto-u.ac.jp/~prims/pdf/41-4/41-4-40.pdf
HankelIntegrator(double nu, double h, double batch=256) :
_nu(nu), _h(h), _Nmax(long(std::min(M_PI/h,1.e6))), _batch(batch), _N(0)
{
xdbg<<"Setup HankelIntegrator for h="<<h<<std::endl;
xdbg<<"Nmax = "<<_Nmax<<std::endl;
// Rarely will we need more than a few hundred values in the sum, even with very
// small values of h (since when that is needed, usually the function is highly
// concentrated at small values of x). So do this in batches of 256.
// Only make the next batch if we end up needing it.
setWeightsBatch();
}
void setWeightsBatch()
{
xdbg<<"Start setWeightsBatch: _N = "<<_N<<std::endl;
// Set up a batch of the weights and kernel values for this choice of h.
long N1 = _N; // Number of values already set.
_N += _batch;
if (_N > _Nmax) _N = _Nmax;
_w.resize(_N);
_x.resize(_N);
for (long i=N1; i<_N; ++i) {
double xi = math::getBesselRoot(_nu,i+1)/M_PI;
double t = _h * xi;
_x[i] = M_PI/_h * psi(t);
_w[i] = math::cyl_bessel_y(_nu, M_PI*xi) / math::cyl_bessel_j(_nu+1, M_PI*xi);
_w[i] *= M_PI * _x[i] * math::cyl_bessel_j(_nu, _x[i]) * dpsi(t);
xdbg<<i<<" "<<xi<<" "<<t<<" "<<_x[i]<<" "<<_w[i]<<std::endl;
}
dbg<<"Done setWeightsBatch: _N = "<<_N<<std::endl;
}
double psi(double t)
{
return t * std::tanh(M_PI/2. * std::sinh(t));
}
double SQR(double x)
{
return x*x;
}
double dpsi(double t)
{
return t * M_PI/2. * std::cosh(t) / SQR(std::cosh(M_PI/2. * std::sinh(t))) + psi(t)/t;
}
double integrate(const std::function<double(double)> f, double k)
{
xdbg<<"start integrate for k = "<<k<<std::endl;
xdbg<<"h, N = "<<_h<<" "<<_N<<std::endl;
assert(_N == long(_w.size()));
assert(_N == long(_x.size()));
double ans = 0.;
long N1 = 0;
bool done = false;
do {
double step = 0.;
for (long i=N1; i<_N; ++i) {
step = _w[i] * f(_x[i]/k);
ans += step;
xdbg<<i<<" "<<_w[i]<<" "<<_x[i]<<" "<<_x[i]/k<<" "<<f(_x[i]/k)<<" "<<step<<" "<<ans<<std::endl;
if (std::abs(step) < 1.e-15 * std::abs(ans)) {
xdbg<<"Break at i = "<<i<<std::endl;
xdbg<<"step = "<<step<<", ans = "<<ans<<std::endl;
done = true;
break;
}
}
N1 = _N;
if (_N == _Nmax) done = true; // Can't go higher than this.
if (step == 0) done = true; // If the steps = 0, we're not going to improve.
if (!done) {
dbg<<"Didn't converge with current values. N="<<_N<<std::endl;
dbg<<"current ans = "<<ans<<", last step = "<<step<<std::endl;
setWeightsBatch();
}
} while (!done);
// We omitted a factor of 1/k^2, so apply that now.
ans /= k*k;
xdbg<<"ans = "<<ans<<std::endl;
return ans;
}
private:
double _nu;
double _h;
long _Nmax;
long _batch;
long _N;
std::vector<double> _w;
std::vector<double> _x;
};
class AdaptiveHankelIntegrator
{
public:
// Wraps the above HankelIntegrator to get requested rel/abs errors.
AdaptiveHankelIntegrator(double nu, double h0=1./32.) : _nu(nu), _h0(h0) {}
HankelIntegrator* get_integrator(double h)
{
if (_integrators.count(h) == 0) {
_integrators[h] = std::unique_ptr<HankelIntegrator>(new HankelIntegrator(_nu,h));
}
return _integrators[h].get();
}
double integrate(const std::function<double(double)> f, double k,
double relerr, double abserr)
{
dbg<<"start adaptive integrate for k = "<<k<<std::endl;
double h0 = _h0;
// Empirically, for very small k, we'll at least need to drop h down to 100*k,
// so do this first before possibly iterating further.
while (h0 > 100*k) h0 *= 0.5;
double ans0 = get_integrator(h0)->integrate(f,k);
double h1 = 0.5 * h0;
double ans1 = get_integrator(h1)->integrate(f,k);
double err = std::abs(ans1-ans0);
dbg<<"first answers = "<<ans0<<", "<<ans1<<" diff = "<<err<<std::endl;
// Three checks for convergence:
// 1. if err < relerr * ans, then we're done
// 2. if err < abserr, then we're done
// 3. ... unless ans1 is much larger than ans0, then we probably don't have a good
// estimate yet, so keep going.
while ((err > relerr * ans1 && (err > abserr || std::abs(ans1) > 2*std::abs(ans0)))
|| ans1 == 0.) {
h0 = h1;
ans0 = ans1;
h1 *= 0.5;
ans1 = get_integrator(h1)->integrate(f,k);
err = std::abs(ans1 - ans0);
dbg<<"answers = "<<ans0<<", "<<ans1<<" diff = "<<err<<std::endl;
}
dbg<<"Final: "<<ans1<<" k,h0 = "<<k<<" "<<h0<<std::endl;
return ans1;
}
private:
double _nu;
double _h0;
std::map<double, std::unique_ptr<HankelIntegrator> > _integrators;
};
double hankel_inf(const std::function<double(double)> f, double k, double nu,
double relerr, double abserr, int nzeros)
{
static std::map<double, std::unique_ptr<AdaptiveHankelIntegrator> > integrators;
dbg<<"Start hankel_inf: "<<k<<" "<<nu<<std::endl;
if (k == 0.) {
// If k = 0, can't do the Ogata method, since it integrates f(x/k) J(x).
return hankel_gkp(f, k, nu, integ::MOCK_INF, relerr, abserr, nzeros);
} else {
if (integrators.count(nu) == 0) {
integrators[nu] = std::unique_ptr<AdaptiveHankelIntegrator>(
new AdaptiveHankelIntegrator(nu));
}
AdaptiveHankelIntegrator* H = integrators[nu].get();
return H->integrate(f, k, relerr, abserr);
}
}
double hankel_trunc(const std::function<double(double)> f, double k, double nu, double rmax,
double relerr, double abserr, int nzeros)
{
dbg<<"Start hankel_trunc: "<<k<<" "<<nu<<" "<<rmax<<std::endl;
return hankel_gkp(f, k, nu, rmax, relerr, abserr, nzeros);
}
}
}