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 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
|
/*
* expint.cpp: exponential integrals.
*/
#include <stdio.h>
#include <stdlib.h>
#include "spigot.h"
#include "funcs.h"
#include "cr.h"
#include "error.h"
#include "holefiller.h"
/*
* Exponential integrals, logarithmic integrals, and the
* Euler-Mascheroni constant (which we get for free as a by-product of
* that lot).
*
* There's a slightly confusing family of exponential integral
* functions. They're all based around the idea of wanting to
* integrate e^x/x, but because that integrand has a pole at 0, things
* get messy.
*
* If you're prepared to work in the complex numbers, then integrands
* with poles are a very well understood problem, which you deal with
* by introducing a branch cut. A typical place to put the branch cut
* is along the negative real axis, which is less than helpful if you
* want a function that restricts sensibly to R (or rather, R\{0}).
* Also, even if you put the branch cut somewhere more out of the way,
* you find that if you want your function to take real values on R^+
* then it ends up taking values on R^- of the form (real + pi i) or
* (real - pi i), depending which way you swerved round the origin.
*
* This is all a bit unsatisfactory for people who are only interested
* in R. So the usual answer is to define a function called Ei, which
* acts as an indefinite integral of e^x/x on each side of zero, so
* that a _definite_ integral over any interval not containing zero
* can be given as the difference of two values of Ei. (If the
* interval does contain zero, of course, you go back to the person
* who asked for it and demand more detail about what exactly they
* were after.) To define this function fully, we must choose an
* additive constant for R^- and another one for R^+ (since the
* disconnected domain permits a separate choice in each component).
*
* The standard definition of Ei, as implemented by octave-specfun
* under the name expint_Ei and by Mathematica under the name
* ExpIntegralEi, chooses the negative domain constant so that the
* limit of Ei at -infinity is zero, and then chooses the positive
* domain constant by 'taking the Cauchy principal value', which
* basically means looking at the integral you'd get if you zeroed out
* a small interval [-epsilon,+epsilon] about the pole at zero, and
* then took the limit as epsilon -> 0. An equivalent way of putting
* that is to say that Ei's negative and positive constants are chosen
* so that lim_{e->0} (Ei(-e) - Ei(+e)) = 0.
*
* The other basic idea of an exponential integral takes two
* parameters, a non-negative integer n and a positive real x.
* MathWorld calls this the 'E_n function':
* http://mathworld.wolfram.com/En-Function.html
* and defines it as
*
* inf exp(-xt)
* E_n(x) = int -------- dt
* 1 t^n
*
* That's not really an illuminating definition as far as I'm
* concerned. I prefer the following inductive definition: E_0 is just
* e^{-x}/x, and then E_{n+1} is the indefinite integral of (-E_n),
* with the constant chosen every time so that every E_n tends to zero
* as x->inf. (The flipping signs are slightly annoying, but the point
* of them seems to be to make all the E_n positive and decreasing,
* rather than having the sign of E_n depend on the parity of n.)
*
* Gnuplot implements E_n(x) under the name expint(n,x), and
* Mathematica under the name ExpIntegralE[n,x].
*
* We implement Ei(x), En(n,x), and E1(x) = En(1,x). We also implement
* a function that Wikipedia defines under the name 'Ein':
* https://en.wikipedia.org/wiki/Exponential_integral which is defined
* as the integral of (1-e^{-x})/x (with constant so that Ein(0)=0).
* The point of that function is that it _doesn't_ have a pole at
* zero, or indeed anywhere else: it's an actually sensible function
* from R->R, and even from C->C, with an obvious power series that
* converges everywhere (obtained from the power series of exp by
* term-by-term integration). So it's easy to evaluate, and hence
* makes a useful primitive to build the various other things out of.
*
* Also in this family are the logarithmic integrals li(x) and Li(x),
* which are easy because they're basically just Ei(log x). Wikipedia
* [https://en.wikipedia.org/wiki/Logarithmic_integral_function]
* defines them to be translations of each other, such that li(0)=0
* but Li(2)=0. (So Li lets you integrate up to large numbers without
* the slight arbitrariness of having to invent a way to handle
* crossing the pole at 1.)
*/
/* ----------------------------------------------------------------------
* Continued-fraction expansion of E_n.
*/
class EnCfrac : public Source {
/*
* http://functions.wolfram.com/GammaBetaErf/ExpIntegralE/10/
* says that
*
* exp(-x)
* E_n(x) = -------------------------
* x + n
* ---------------------
* 1 + 1
* -----------------
* x + n + 1
* -------------
* 1 + 2
* ---------
* x + n + 2
* -----
* ...
*
* This class computes everything but the exp(-x) factor, which
* the caller must multiply in afterwards.
*/
bigint xn, xd, n, nim1, i;
int crState;
public:
EnCfrac(const bigint &axn, const bigint &axd, const bigint &an)
: xn(axn), xd(axd), n(an)
{
crState = -1;
}
virtual EnCfrac *clone() { return new EnCfrac(xn, xd, n); }
bool gen_interval(bigint *low, bigint *high)
{
*low = 0;
*high = 0;
return false;
}
bool gen_matrix(bigint *matrix)
{
crBegin;
/*
* An initial matrix representing x |-> 1/x.
*/
matrix[0] = 0;
matrix[1] = 1;
matrix[2] = 1;
matrix[3] = 0;
crReturn(false);
/*
* Then the regular series.
*/
i = 1;
nim1 = n; // n + i - 1
while (1) {
matrix[0] = xn;
matrix[1] = nim1*xd;
matrix[2] = xd;
matrix[3] = 0;
crReturn(false);
matrix[0] = 1;
matrix[1] = i;
matrix[2] = 1;
matrix[3] = 0;
crReturn(false);
++i;
++nim1;
}
crEnd;
}
};
class EinSeries : public Source {
/*
* Ein is computed by the following power series:
*
* inf (-1)^{k+1} x^k
* Ein(x) = sum --------------
* k=1 k k!
*
* Let x = n/d. Then we have
*
* n -n^2 +n^3
* Ein(n/d) = ------ + -------- + -------- + ...
* 1 1! d 2 2! d^2 3 3! d^3
*
* n -n 1 -n 1
* = - ( 1 + -- ( - + -- ( - + ... )))
* d 2d 2 3d 3
*
* so our matrices go (with an anomalous non-negation of n in the
* first one)
*
* ( n n ) ( -2n -n ) ( -3n -n ) ...
* ( 0 d ) ( 0 4d ) ( 0 9d )
*/
bigint n, d, k;
int crState;
public:
EinSeries(const bigint &an, const bigint &ad)
: n(an), d(ad)
{
crState = -1;
}
virtual EinSeries *clone() { return new EinSeries(n, d); }
bool gen_interval(bigint *low, bigint *high)
{
*low = -1;
*high = 1;
return true;
}
bool gen_matrix(bigint *matrix)
{
crBegin;
/* Anomalous initial matrix without n negated */
matrix[0] = n;
matrix[1] = n;
matrix[2] = 0;
matrix[3] = d;
crReturn(false);
k = 2;
while (1) {
matrix[0] = -k*n;
matrix[1] = -n;
matrix[2] = 0;
matrix[3] = k*k*d;
++k;
crReturn(false);
}
crEnd;
}
};
struct EnCfracConstructor : MonotoneConstructor {
bigint n;
EnCfracConstructor(const bigint &an) : n(an) {}
MonotoneConstructor *clone() { return new EnCfracConstructor(n); }
Spigot *construct(const bigint &xn, const bigint &xd) {
return new EnCfrac(xn, xd, n);
}
};
struct EinSeriesConstructor : MonotoneConstructor {
MonotoneConstructor *clone() { return new EinSeriesConstructor(); }
Spigot *construct(const bigint &n, const bigint &d) {
return new EinSeries(n, d);
}
};
static Spigot *spigot_En_internal(const bigint &n, Spigot *x)
{
Spigot *expintcfrac = spigot_monotone(new EnCfracConstructor(n),
x->clone());
return spigot_mul(expintcfrac, spigot_exp(spigot_neg(x)));
}
Spigot *spigot_Ein(Spigot *x)
{
return spigot_monotone(new EinSeriesConstructor, x->clone());
}
/*
* Wikipedia shows the relation between E_1 and Ein as
*
* E_1(z) = -gamma - log(z) + Ein(z)
*
* where gamma is the Euler-Mascheroni constant. This means that using
* our continued fraction for E_1 and our series for Ein, we can
* _compute_ the Euler-Mascheroni constant!
*/
Spigot *spigot_eulergamma()
{
/*
* This formula works for any z, so it's a matter of choosing one
* which gives the best convergence.
*
* We care about generating a lot of digits of gamma, of course;
* but we also care about not taking too long to _get started_,
* because we'll also be using this as a component of some of the
* user-facing exponential integral functions below. Benchmarking
* suggests that as you turn up z, you get a gradual improvement
* in the generation of lots of digits, but it gets more and more
* gradual and at the same time the startup time begins to become
* significant - by the time you're at z=4096, say, you're taking
* (on my test machine) 12 seconds before _any_ digit is
* generated, and not managing to overtake the z=512 run until
* about 1000 digits have gone by, and even then, not by very
* much.
*
* I suppose if you wanted a _really_ large number of digits, it
* _might_ be worth rigging up a wrapper class which kept
* restarting the computation from the beginning with bigger and
* bigger z, akin to the strategies used by GammaResidualBase and
* MonotoneHelper, but I think the gain would be marginal even so.
* Based on my ad-hoc benchmarking, the sensible thing seems to be
* to pick the largest value of z that we get to before the
* startup time starts to become noticeable, and that's 256.
*/
Spigot *z = spigot_integer(256);
Spigot *Ein_minus_En = spigot_sub(spigot_Ein(z->clone()),
spigot_En_internal(1, z->clone()));
return spigot_sub(Ein_minus_En, spigot_log(z));
}
class EnHoleFiller : public HoleFiller {
bigint n;
virtual EnHoleFiller *clone() { return new EnHoleFiller(n, x->clone()); }
virtual Spigot *xspecial(int i) {
if (i == 0 && n > 1) return spigot_integer(0);
return NULL;
}
virtual Spigot *yspecial(int i) {
if (i == 0 && n > 1) return spigot_rational(1, n-1);
return NULL;
}
virtual Spigot *ygeneral(Spigot *x) {
return spigot_En_internal(n, x);
}
public:
EnHoleFiller(const bigint &an, Spigot *ax) : HoleFiller(ax), n(an) {}
};
Spigot *spigot_En(Spigot *n, Spigot *x)
{
bigint nn, nd;
if (!n->is_rational(&nn, &nd) || nd != 1)
throw spigot_error("first argument to En must be an integer");
if (nn < 0)
throw spigot_error("first argument to En must be non-negative");
if (nn > 1)
x = spigot_enforce(x, ENFORCE_GE, spigot_integer(0),
spigot_error("second argument to En must be "
"non-negative"));
else
x = spigot_enforce(x, ENFORCE_GT, spigot_integer(0),
spigot_error("second argument to En must be "
"positive"));
return (new EnHoleFiller(nn, x))->replace();
}
Spigot *spigot_E1(Spigot *x)
{
x = spigot_enforce(x, ENFORCE_GT, spigot_integer(0),
spigot_error("argument to E1 must be positive"));
return spigot_En_internal(1, x);
}
Spigot *spigot_Ei(Spigot *x)
{
Spigot *Ein_term = spigot_Ein(spigot_neg(x->clone()));
return spigot_sub(spigot_add(spigot_eulergamma(),
spigot_log(spigot_abs(x))),
Ein_term);
}
// Unusually lowercase class name, because li and Li really are
// different things :-)
class liHoleFiller : public HoleFiller {
virtual liHoleFiller *clone() { return new liHoleFiller(x->clone()); }
virtual Spigot *xspecial(int i) {
if (i == 0) return spigot_integer(0);
return NULL;
}
virtual Spigot *yspecial(int i) {
if (i == 0) return spigot_integer(0);
return NULL;
}
virtual Spigot *ygeneral(Spigot *x) {
return spigot_Ei(spigot_log(x));
}
public:
liHoleFiller(Spigot *ax) : HoleFiller(ax) {}
};
Spigot *spigot_li(Spigot *x)
{
x = spigot_enforce(x, ENFORCE_GE, spigot_integer(0),
spigot_error("argument to li must be non-negative"));
return (new liHoleFiller(x))->replace();
}
class LiHoleFiller : public HoleFiller {
virtual LiHoleFiller *clone() { return new LiHoleFiller(x->clone()); }
virtual Spigot *xspecial(int i) {
if (i == 0) return spigot_integer(0);
if (i == 1) return spigot_integer(2);
return NULL;
}
virtual Spigot *yspecial(int i) {
if (i == 0) return spigot_neg(spigot_li(spigot_integer(2)));
if (i == 1) return spigot_integer(0);
return NULL;
}
virtual Spigot *ygeneral(Spigot *x) {
/*
* Li is just li(x)-li(2), or equivalently, the integral from 2 to
* x of t/log(t). But we can do better than actually subtracting
* two values of li, because li is implemented in terms of Ei
* which is in turn the sum of multiple terms, so we can simplify:
*
* Li(x) = li(x) - li(2)
* = Ei(log x) - Ei(log 2)
* = gamma + log |log x| - Ein(-log x)
* - gamma - log |log 2| + Ein(-log 2)
* = log |log x| - log |log 2| - Ein(-log x) + Ein(-log 2)
* = log (|log_2 x|) - Ein(-log x) + Ein(-log 2)
*
* and now we don't have to compute gamma at all, plus the two
* separate log terms have turned into a single log-base-2.
*/
Spigot *log_log2_x = spigot_log(spigot_abs(spigot_log2(x->clone())));
Spigot *Ein_log_x = spigot_Ein(spigot_neg(spigot_log(x)));
Spigot *Ein_log_2 = spigot_Ein(spigot_log(spigot_rational(1,2)));
return spigot_add(log_log2_x, spigot_sub(Ein_log_2, Ein_log_x));
}
public:
LiHoleFiller(Spigot *ax) : HoleFiller(ax) {}
};
Spigot *spigot_Li(Spigot *x)
{
x = spigot_enforce(x, ENFORCE_GE, spigot_integer(0),
spigot_error("argument to Li must be non-negative"));
return (new LiHoleFiller(x))->replace();
}
|