Program Listing for File gaussquad.cpp¶
↰ Return to documentation for file (/Users/robertshaw/devfiles/libecpint/src/lib/gaussquad.cpp
)
/*
* Copyright (c) 2020 Robert Shaw
* This file is a part of Libecpint.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "gaussquad.hpp"
#include <cmath>
#include <cassert>
#include <iostream>
namespace libecpint {
// Constructor
GCQuadrature::GCQuadrature() {
// Currently does nothing
}
GCQuadrature::GCQuadrature(const GCQuadrature &other) {
maxN = other.maxN;
M = other.M;
I = other.I;
t = other.t;
x = other.x;
w = other.w;
}
// Initialise the quadrature grid
// As described in both Perez92 and Perez93
void GCQuadrature::initGrid(const int points, const GCTYPE _t) {
t = _t;
// Initialise parameters for grid
int p;
if (t == ONEPOINT) { // Perez92 one point method
// We need the number of points to be of the form
// 2^p - 1 for some power p.
p = (int) floor(log(points + 1)/log(2));
maxN = pow(2, p) - 1;
} else if (t == TWOPOINT) { // Perez93 two point method
// Here we need it instead to be of the form
// 3 * 2^p - 1 for some p.
p = (int) floor(log((points + 2)/3.0)/log(2));
maxN = 3*pow(2, p) - 1;
}
M = (maxN-1)/2; // Midpoint
// initialise arrays
x.assign(maxN, 0.0);
w.assign(maxN, 0.0);
// At the midpoint, M, x[M] = 0 and w[M] = 1
x[M] = 0.0; w[M] = 1.0;
// The rest of the abscissae and weights are then given by:
// z_i = i*Pi / (maxN + 1), s_i = sin(z_i), c_i = cos(z_i)
// x_i = 1 + 2/(3*pi) * [ (3 + 2*s_i^2) * c_i * s_i - 3z_i]
// (3(maxN + 1)/16) * w_i = s_i^4
// We then note that s_(i+1) = c_1 s_i + s_1 c_i
// and c_(i+1) = c_1 c_i - s_1 s_i
// with z_(i+1) = z_i + z_1
// Clearly s_(maxN + 1 - i) = s_i
// and c_(maxN + 1 - i) = -c_i
// Therefore x_(maxN + 1 - i) = -x_i
// and w_(maxN + 1 - i) = w_i
// Meaning that we only have to calculate half the weights and abscissae
double z1 = M_PI / ((double)(maxN + 1));
double c1 = cos(z1); double s1 = sin(z1);
double zi, si, ci, zi1, si1, ci1; //z_i, s_i, c_i, z_(i+1), s_(i+1), c_(i+1)
zi1 = z1; si1 = s1; ci1 = c1;
double o23pi = 2.0 / (3.0 * M_PI); // Convenient
double s2; //si * si
for (int n = 0; n < M; n++) {
// First update zi, si, ci
zi = zi1;
si = si1;
ci = ci1;
s2 = si * si;
// Now determine the w and x values
w[maxN - 1 - n] = w[n] = s2 * s2;
x[n] = 1 + o23pi * ( (3.0 + 2.0 * s2) * ci * si - 3.0*zi );
x[maxN - 1 - n] = x[n];
x[n] = -x[n];
// Then update zi1, si1, ci1
zi1 = zi + z1;
si1 = c1 * si + s1 * ci;
ci1 = c1 * ci - s1 * si;
}
/*std::cout << maxN << " " << M << " " << start << " " << end << "\n";
for (int q = 0; q < maxN; q++) std::cout << x[q] << " " << w[q] << "\n";*/
}
// Perform the GC integration on the function f
std::pair<double, bool> GCQuadrature::integrate(
std::function<double(double, const double*, int)> &f, const double *params,
const double tolerance, const int start, const int end) const {
bool converged = false; // 0 for converged, -1 for not converged
double I = 0;
if (t == ONEPOINT) {
// Perez92 Case
// Integration proceeds in the sequence T_1, T_3, T_7, ..., T_{maxN}
// where T_m = (3(m+1)/16)I_m
// by using the fact that T_{2m + 1} = T_{m} + sum_{k = 0}^m w_{2k+1}f(x_{2k+1})
// The indices in terms of the maxN indices are given by
// 2k + 1 = (2k + 1) * M / 2^n = (2k + 1) * p
// and checking convergence via whether
// (T_{2m + 1} - 2T_m)^2 <= |T_{2m+1} - 4T_{(m-1)/2}| x tolerance
double Tn, T2n1, Tn12; // T_n, T_{2n+1} and 4T_{(n-1)/2}
// Initialise values,
// Single point integration would use midpoint, M
Tn = w[M]*f(x[M], params, M);
Tn12 = 2.0 * Tn;
// Main loop
int n = 1;
double dT; // T_{2n+1} - 2T_n
int ix; // Index needs to be calculated to know which points to use
int p = (M+1) / 2; // M / 2^n
while (n < maxN && !converged) {
// Compute T_{2n+1}
T2n1 = Tn + sumTerms(f, params, n, start, end, p, 2);
// Check convergence
dT = T2n1 - 2.0*Tn;
n = 2*n + 1;
if (dT*dT <= fabs(T2n1 - Tn12)*tolerance) {
converged = true;
} else {
Tn12 = 4.0 * Tn;
Tn = T2n1;
p /= 2;
}
}
// Finalise the integral
I = 16.0 * T2n1 / (3.0*(n + 1.0));
} else if (t == TWOPOINT) {
// Perez93 case
// We instead proceed along T_2, T_5, T_11, ..., T_{2m + 1} where maxN = 2m+1
// but also compute T_1, T_3, ..., T_n, T_{2n+1} etc. as before,
// where m + 1 = 3/2(n+1), so as to get better error control
// To do this, we use that
// T_{2m+1} = T_m + T_n - T_{(n-1)/2} + sum_{i=0}^{(m-2)/3} [w_{6i+1}f(x_{6i+1}) + w_{6i+5}f(x_{6i+5})]
// along with the same results as before.
// The algorithm proceeds by calculating one in the two-point sequence,
// using an error of |I_{2m+1} - I_m|, then calculates one in the one-point sequence
// and uses an error of |I_m - I_n|, to check convergence.
double Tn, Tm, T2n1, T2m1, Tn12;
// Initialise values
Tn12 = 0.0;
Tn = w[M]*f(x[M], params, M);
int M2 = (maxN - 2)/3; //Index of first point in twopoint sequence
Tm = w[M2]*f(x[M2], params, M2) + w[maxN - M2 - 1]*f(x[maxN - M2 - 1], params, maxN - M2 - 1);
int p = (M+1) / 2; // as before
M2 = (M2 + 1)/2;
int ix;
int n = 1; int m = 2;
double error;
while(m < maxN && !converged) {
// Propagate the two-point sequence first
T2m1 = Tm + Tn - Tn12 + sumTerms(f, params, (2*m - 1)/3, start, end, M2, 3);
// Check convergence
error = 16.0 * fabs(0.5*T2m1 - Tm) / (3.0 * (m + 1));
if (error > tolerance) {
// Propagate the one-point sequence
T2n1 = Tn + sumTerms(f, params, n, start, end, p, 2);
// Check convergence again
error = 16.0 * fabs(2.0*T2m1 - 3.0*T2n1) / (18.0 * (n+1) );
m = 2 * m + 1;
n = 2 * n + 1;
if ( error < tolerance) {
converged = true;
} else {
Tn12 = Tn;
Tn = T2n1;
Tm = T2m1;
p /= 2;
M2 /= 2;
}
} else {
m = 2 * m + 1;
converged = true;
}
}
// Finalise the integral
I = 16.0 * T2m1 / (3.0 * (m + 1.0));
}
return {I, converged};
}
// Worker function to do the additional sum terms when going from I_n to I_{2n+1}
double GCQuadrature::sumTerms(
const std::function<double(double, const double*, int)> &f,
const double *p, const int limit, const int start, const int end,
const int shift, const int skip) const {
assert(start >= 0 && start < maxN);
assert(end >= 0 && end < maxN);
assert(end >= start);
double value = 0.0;
int ix;
for (int i = 0; i <= limit; i+=2) {
ix = (skip*i + 1)*shift - 1;
if (ix >= start)
value += w[ix] * f(x[ix], p, ix);
ix = maxN - ix - 1;
if (ix <= end)
value += w[ix] * f(x[ix], p, ix);
}
return value;
}
// The GC integrations above are over the interval [-1, 1] and thus need to be transformed
// to the interval[0, infty), or [rmin, rmax]. We do this by the logarithmic transformation from Krack98
// or the linear mapping of Flores06, respectively.
void GCQuadrature::transformZeroInf() {
double ln2 = log(2.0);
double xt;
for (int i = 0; i < maxN; i++) {
xt = 1.0 - log(1.0-x[i])/ln2;
w[i] = w[i]/(ln2 * (1.0 - x[i]));
x[i] = xt;
}
}
void GCQuadrature::transformRMinMax(const double z, const double p) {
double osz = 1.0 / sqrt(z);
// Determine interval
double rmin = p - 7.0 * osz;
rmin = rmin > 0 ? rmin : 0.0;
double rmax = p + 9.0 * osz;
// Find the relative and absolute midpoints
double rmid = 0.5*(rmax - rmin); // Midpoint of interval relative to rmin
double amid = rmid + rmin; // Midpoint of interval
// Transform weights and abscissae by linearly transforming
// both are scaled by rmid, and the abscissae are translated by amid
for (int i = 0; i < maxN; i++) {
x[i] = rmid * x[i] + amid;
w[i] *= rmid;
}
}
void GCQuadrature::untransformRMinMax(const double z, const double p) {
double osz = 1.0 / sqrt(z);
// Determine interval
double rmin = p - 7.0 * osz;
rmin = rmin > 0 ? rmin : 0.0;
double rmax = p + 9.0 * osz;
// Find the relative and absolute midpoints
double rmid = 0.5*(rmax - rmin); // Midpoint of interval relative to rmin
double amid = rmid + rmin; // Midpoint of interval
// Transform weights and abscissae by linearly transforming
// both are scaled by rmid, and the abscissae are translated by amid
for (int i = 0; i < maxN; i++) {
x[i] = (x[i] - amid) / rmid;
w[i] /= rmid;
}
}
}