/* Adaptive multidimensional integration of a vector of integrands. * * Copyright (c) 2005-2013 Steven G. Johnson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* p-adaptive cubature (adaptive by increasing the degree of the cubature rule rather than subdividing the domain), using products of Clenshaw-Curtis rules. This algorithm may be superior to Genz-Malik for smooth integrands lacking strongly-localized features, in moderate dimensions. */ #include #include #include #include "cubature.h" /* error return codes */ #define SUCCESS 0 #define FAILURE 1 /* pre-generated Clenshaw-Curtis rules and weights */ #include "clencurt.h" /* no point in supporting very high dimensional integrals here */ #define MAXDIM (20U) /***************************************************************************/ /* For adaptive cubature, thanks to the nesting of the C-C rules, we can re-use the values from coarser grids for finer grids, and the coarser grids are also used for error estimation. A grid is determined by an m[dim] array, where m[i] denotes 2^(m[i]+1)+1 points in the i-th dimension. */ /* cache of the values for the m[dim] grid. If mi < dim, then we only store the values corresponding to the difference between the m grid and the grid with m[mi] -> m[mi]-1. (m[mi]-1 == -1 corresponds to the trivial grid of one point in the center.) */ typedef struct cacheval_s { unsigned m[MAXDIM]; unsigned mi; double *val; } cacheval; /* array of ncache cachevals c[i] */ typedef struct valcache_s { size_t ncache; cacheval *c; } valcache; static void free_cachevals(valcache *v) { if (!v) return; if (v->c) { size_t i; for (i = 0; i < v->ncache; ++i) free(v->c[i].val); free(v->c); v->c = NULL; } v->ncache = 0; } /***************************************************************************/ /* recursive loop over all cubature points for the given (m,mi) cache entry: add each point to the buffer buf, evaluating all at once whenever the buffer is full or when we are done */ static int compute_cacheval(const unsigned *m, unsigned mi, double *val, size_t *vali, unsigned fdim, integrand_v f, void *fdata, unsigned dim, unsigned id, double *p, const double *xmin, const double *xmax, double *buf, size_t nbuf, size_t *ibuf) { if (id == dim) { /* add point to buffer of points */ memcpy(buf + (*ibuf)++ * dim, p, sizeof(double) * dim); if (*ibuf == nbuf) { /* flush buffer */ if (f(dim, nbuf, buf, fdata, fdim, val + *vali)) return FAILURE; *vali += *ibuf * fdim; *ibuf = 0; } } else { double c = (xmin[id] + xmax[id]) * 0.5; double r = (xmax[id] - xmin[id]) * 0.5; const double *x = clencurt_x + ((id == mi) ? (m[id] ? (1 << (m[id] - 1)) : 0) : 0); unsigned i, nx = (id == mi ? (m[id] ? (1 << (m[id] - 1)) : 1) : (1 << (m[id]))); if (id != mi) { p[id] = c; if (compute_cacheval(m, mi, val, vali, fdim, f, fdata, dim, id + 1, p, xmin, xmax, buf, nbuf, ibuf)) return FAILURE; } for (i = 0; i < nx; ++i) { p[id] = c + r * x[i]; if (compute_cacheval(m, mi, val, vali, fdim, f, fdata, dim, id + 1, p, xmin, xmax, buf, nbuf, ibuf)) return FAILURE; p[id] = c - r * x[i]; if (compute_cacheval(m, mi, val, vali, fdim, f, fdata, dim, id + 1, p, xmin, xmax, buf, nbuf, ibuf)) return FAILURE; } } return SUCCESS; } static size_t num_cacheval(const unsigned *m, unsigned mi, unsigned dim) { unsigned i; size_t nval = 1; for (i = 0; i < dim; ++i) { if (i == mi) nval *= m[i] == 0 ? 2 : (1 << (m[i])); else nval *= (1 << (m[i] + 1)) + 1; } return nval; } static int add_cacheval(valcache *vc, const unsigned *m, unsigned mi, unsigned fdim, integrand_v f, void *fdata, unsigned dim, const double *xmin, const double *xmax, double *buf, size_t nbuf) { size_t ic = vc->ncache; size_t nval, vali = 0, ibuf = 0; double p[MAXDIM]; vc->c = (cacheval *) realloc(vc->c, sizeof(cacheval) * ++(vc->ncache)); if (!vc->c) return -1; vc->c[ic].mi = mi; memcpy(vc->c[ic].m, m, sizeof(unsigned) * dim); nval = fdim * num_cacheval(m, mi, dim); vc->c[ic].val = (double *) malloc(sizeof(double) * nval); if (!vc->c[ic].val) return FAILURE; if (compute_cacheval(m, mi, vc->c[ic].val, &vali, fdim, f, fdata, dim, 0, p, xmin, xmax, buf, nbuf, &ibuf)) return FAILURE; if (ibuf > 0) /* flush remaining buffer */ return f(dim, ibuf, buf, fdata, fdim, vc->c[ic].val + vali); return SUCCESS; } /***************************************************************************/ /* recursive loop to evaluate the integral contribution from the cache entry c, accumulating in val, for the given m[] except with m[md] -> m[md] - 1 if md < dim, using the cached values (cm,cmi,cval). id is the current loop dimension (from 0 to dim-1). */ static unsigned eval(const unsigned *cm, unsigned cmi, double *cval, const unsigned *m, unsigned md, unsigned fdim, unsigned dim, unsigned id, double weight, double *val) { size_t voff = 0; /* amount caller should offset cval array afterwards */ if (id == dim) { unsigned i; for (i = 0; i < fdim; ++i) val[i] += cval[i] * weight; voff = fdim; } else if (m[id] == 0 && id == md) /* using trivial rule for this dim */ { voff = eval(cm, cmi, cval, m, md, fdim, dim, id+1, weight*2, val); voff += fdim * (1 << cm[id]) * 2 * num_cacheval(cm + id+1, cmi - (id+1), dim - (id+1)); } else { unsigned i; unsigned mid = m[id] - (id == md); /* order of C-C rule */ const double *w = clencurt_w + mid + (1 << mid) - 1 + (id == cmi ? (cm[id] ? 1 + (1 << (cm[id]-1)) : 1) : 0); unsigned cnx = (id == cmi ? (cm[id] ? (1 << (cm[id]-1)) : 1) : (1 << (cm[id]))); unsigned nx = cm[id] <= mid ? cnx : (1 << mid); if (id != cmi) { voff = eval(cm, cmi, cval, m, md, fdim, dim, id + 1, weight * w[0], val); ++w; } for (i = 0; i < nx; ++i) { voff += eval(cm, cmi, cval + voff, m, md, fdim, dim, id + 1, weight * w[i], val); voff += eval(cm, cmi, cval + voff, m, md, fdim, dim, id + 1, weight * w[i], val); } voff += (cnx - nx) * fdim * 2 * num_cacheval(cm + id+1, cmi - (id+1), dim - (id+1)); } return voff; } /* loop over all cache entries that contribute to the integral, (with m[md] decremented by 1) */ static void evals(valcache vc, const unsigned *m, unsigned md, unsigned fdim, unsigned dim, double V, double *val) { size_t i; memset(val, 0, sizeof(double) * fdim); for (i = 0; i < vc.ncache; ++i) { if (vc.c[i].mi >= dim || vc.c[i].m[vc.c[i].mi] + (vc.c[i].mi == md) <= m[vc.c[i].mi]) eval(vc.c[i].m, vc.c[i].mi, vc.c[i].val, m, md, fdim, dim, 0, V, val); } } /* evaluate the integrals for the given m[] using the cached values in vc, storing the integrals in val[], the error estimate in err[], and the dimension to subdivide next (the largest error contribution) in *mi */ static void eval_integral(valcache vc, const unsigned *m, unsigned fdim, unsigned dim, double V, unsigned *mi, double *val, double *err, double *val1) { double maxerr = 0; unsigned i, j; evals(vc, m, dim, fdim, dim, V, val); /* error estimates along each dimension by comparing val with lower-order rule in that dimension; overall (conservative) error estimate from maximum error of lower-order rules. */ memset(err, 0, sizeof(double) * fdim); *mi = 0; for (i = 0; i < dim; ++i) { double emax = 0; evals(vc, m, i, fdim, dim, V, val1); for (j = 0; j < fdim; ++j) { double e = fabs(val[j] - val1[j]); if (e > emax) emax = e; if (e > err[j]) err[j] = e; } if (emax > maxerr) { maxerr = emax; *mi = i; } } /* printf("eval: %g +/- %g (dim %u)\n", val[0], err[0], *mi); */ } /***************************************************************************/ static int converged(unsigned fdim, const double *vals, const double *errs, double reqAbsError, double reqRelError, error_norm norm) #define ERR(j) errs[j] #define VAL(j) vals[j] #include "converged.h" /***************************************************************************/ /* Vectorized version with user-supplied buffer to store points and values. The buffer *buf should be of length *nbuf * dim on entry (these parameters are changed upon return to the final buffer and length that was used). The buffer length will be kept <= max(max_nbuf, 1) * dim. Also allows the caller to specify an array m[dim] of starting degrees for the rule, which upon return will hold the final degrees. The number of points in each dimension i is 2^(m[i]+1) + 1. */ int pcubature_v_buf(unsigned fdim, integrand_v f, void *fdata, unsigned dim, const double *xmin, const double *xmax, size_t maxEval, double reqAbsError, double reqRelError, error_norm norm, unsigned *m, double **buf, size_t *nbuf, size_t max_nbuf, double *val, double *err) { int ret = FAILURE; double V = 1; size_t numEval = 0, new_nbuf; unsigned i; valcache vc = {0, NULL}; double *val1 = NULL; if (fdim <= 1) norm = ERROR_INDIVIDUAL; /* norm is irrelevant */ if (norm < 0 || norm > ERROR_LINF) return FAILURE; /* invalid norm */ if (fdim == 0) return SUCCESS; /* nothing to do */ if (dim > MAXDIM) return FAILURE; /* unsupported */ if (dim == 0) { /* trivial case */ if (f(0, 1, xmin, fdata, fdim, val)) return FAILURE; for (i = 0; i < fdim; ++i) err[i] = 0; return SUCCESS; } for (i = 0; i < fdim; ++i) { val[i] = 0; err[i] = HUGE_VAL; } for (i = 0; i < dim; ++i) V *= (xmax[i] - xmin[i]) * 0.5; /* scale factor for C-C volume */ new_nbuf = num_cacheval(m, dim, dim); if (max_nbuf < 1) max_nbuf = 1; if (new_nbuf > max_nbuf) new_nbuf = max_nbuf; if (*nbuf < new_nbuf) { free(*buf); *buf = (double *) malloc(sizeof(double) * (*nbuf = new_nbuf) * dim); if (!*buf) goto done; } /* start by evaluating the m=0 cubature rule */ if (add_cacheval(&vc, m, dim, fdim, f, fdata, dim, xmin, xmax, *buf, *nbuf) != SUCCESS) goto done; val1 = (double *) malloc(sizeof(double) * fdim); while (1) { unsigned mi; eval_integral(vc, m, fdim, dim, V, &mi, val, err, val1); if (converged(fdim, val, err, reqAbsError, reqRelError, norm) || (numEval > maxEval && maxEval)) { ret = SUCCESS; goto done; } m[mi] += 1; if (m[mi] > clencurt_M) goto done; /* FAILURE */ new_nbuf = num_cacheval(m, mi, dim); if (new_nbuf > *nbuf && *nbuf < max_nbuf) { *nbuf = new_nbuf; if (*nbuf > max_nbuf) *nbuf = max_nbuf; free(*buf); *buf = (double *) malloc(sizeof(double) * *nbuf * dim); if (!*buf) goto done; /* FAILURE */ } if (add_cacheval(&vc, m, mi, fdim, f, fdata, dim, xmin, xmax, *buf, *nbuf) != SUCCESS) goto done; /* FAILURE */ numEval += new_nbuf; } done: free(val1); free_cachevals(&vc); return ret; } /***************************************************************************/ #define DEFAULT_MAX_NBUF (1U << 20) int pcubature_v(unsigned fdim, integrand_v f, void *fdata, unsigned dim, const double *xmin, const double *xmax, size_t maxEval, double reqAbsError, double reqRelError, error_norm norm, double *val, double *err) { int ret; size_t nbuf = 0; unsigned m[MAXDIM]; double *buf = NULL; memset(m, 0, sizeof(unsigned) * dim); ret = pcubature_v_buf(fdim, f, fdata, dim, xmin, xmax, maxEval, reqAbsError, reqRelError, norm, m, &buf, &nbuf, DEFAULT_MAX_NBUF, val, err); free(buf); return ret; } #include "vwrapper.h" int pcubature(unsigned fdim, integrand f, void *fdata, unsigned dim, const double *xmin, const double *xmax, size_t maxEval, double reqAbsError, double reqRelError, error_norm norm, double *val, double *err) { int ret; size_t nbuf = 0; unsigned m[MAXDIM]; double *buf = NULL; fv_data d; d.f = f; d.fdata = fdata; memset(m, 0, sizeof(unsigned) * dim); ret = pcubature_v_buf( fdim, fv, &d, dim, xmin, xmax, maxEval, reqAbsError, reqRelError, norm, m, &buf, &nbuf, 16 /* max_nbuf > 0 to amortize function overhead */, val, err); free(buf); return ret; }