/*
 * 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.
 */

/*
 * 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.
 */
class EnCfrac : public Source {
    bigint xn, xd, n, nim1, i;
    int crState { -1 };

    virtual Spigot clone() override;
    virtual bool gen_interval(bigint *low, bigint_or_inf *high) override;
    virtual bool gen_matrix(Matrix &matrix) override;

  public:
    EnCfrac(const bigint &axn, const bigint &axd, const bigint &an);
};

MAKE_CLASS_GREETER(EnCfrac);

EnCfrac::EnCfrac(const bigint &axn, const bigint &axd, const bigint &an)
    : xn(axn), xd(axd), n(an)
{
    dgreet(xn, "/", xd, " n=", n);
}

Spigot EnCfrac::clone()
{
    return spigot_clone(this, xn, xd, n);
}

bool EnCfrac::gen_interval(bigint *low, bigint_or_inf *high)
{
    *low = 0;
    *high = bigint_or_inf::infinity;
    return false;
}

bool EnCfrac::gen_matrix(Matrix &matrix)
{
    crBegin;

    /*
     * An initial matrix representing x |-> 1/x.
     */
    matrix = { 0, 1, 1, 0 };
    crReturn(false);

    /*
     * Then the regular series.
     */
    i = 1;
    nim1 = n;                      // n + i - 1

    while (1) {
        matrix = { xn, nim1*xd, xd, 0 };
        crReturn(false);

        matrix = { 1, i, 1, 0 };
        crReturn(false);

        ++i;
        ++nim1;
    }

    crEnd;
}

/*
 * 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 )
 */
class EinSeries : public Source {
    bigint n, d, k;
    int crState { -1 };
    bool force;

    virtual Spigot clone() override;
    virtual bool gen_interval(bigint *low, bigint_or_inf *high) override;
    virtual bool gen_matrix(Matrix &matrix) override;

  public:
    EinSeries(const bigint &an, const bigint &ad);
};

MAKE_CLASS_GREETER(EinSeries);

EinSeries::EinSeries(const bigint &an, const bigint &ad)
    : n(an), d(ad)
{
    dgreet(n, "/", d);
}

Spigot EinSeries::clone()
{
    return spigot_clone(this, n, d);
}

bool EinSeries::gen_interval(bigint *low, bigint_or_inf *high)
{
    *low = -1;
    *high = 1;
    return true;
}

bool EinSeries::gen_matrix(Matrix &matrix)
{
    crBegin;

    /*
     * We have to be a bit careful about whether these matrices
     * are refining (i.e. whether they map the starting interval
     * [-1,+1] to a subset of itself). Some initial segment of
     * this series consists of non-refining matrices, and as long
     * as we're in that segment, we must return 'true' to force
     * our consuming Core to keep fetching matrices until we do
     * start refining.
     *
     * The form of these matrices, with both numerators and
     * denominators growing monotonically but the former linearly
     * and the latter quadratically, guarantees that once we find
     * _one_ refining matrix, all the ones after that will be safe
     * too.
     */
    force = true;

    /* Anomalous initial matrix without n negated */
    matrix = { n, n, 0, d };

    if (force && bigint_abs(matrix[0] + matrix[1]) <= matrix[3])
        force = false;
    dprint("initial matrix ", matrix, ", force=", force);

    crReturn(force);

    k = 2;
    while (1) {
        matrix = { -k*n, -n, 0, k*k*d };

        if (force && bigint_abs(matrix[0] + matrix[1]) <= matrix[3])
            force = false;
        dprint("k=", k, ": matrix ", matrix, ", force=", force);

        crReturn(force);
        ++k;
    }

    crEnd;
}

class EnCfracConstructor : public MonotoneConstructor {
    bigint n;
    virtual Spigot construct(const bigint &xn, const bigint &xd) override;
  public:
    EnCfracConstructor(const bigint &an);
};

EnCfracConstructor::EnCfracConstructor(const bigint &an)
    : n(an)
{
}

Spigot EnCfracConstructor::construct(const bigint &xn, const bigint &xd)
{
    return make_unique<EnCfrac>(xn, xd, n);
}

static Spigot EnCfrac_debug(Spigot x, Spigot n_sp)
{
    bigint n, d;
    if (!n_sp->is_rational(&n, &d) || d != 1)
        throw expr_error("second parameter must be an integer");
    return spigot_monotone(
        make_shared<EnCfracConstructor>(n), move(x));
}

static const BinaryDebugFnWrapper expr_EnCfrac("EnCfrac", EnCfrac_debug);

class EinSeriesConstructor : public MonotoneConstructor {
    virtual Spigot construct(const bigint &n, const bigint &d) override;
};

Spigot EinSeriesConstructor::construct(const bigint &n, const bigint &d)
{
    return make_unique<EinSeries>(n, d);
}

static Spigot spigot_En_internal(const bigint &n, Spigot x)
{
    Spigot expintcfrac = spigot_monotone(
        make_shared<EnCfracConstructor>(n), x->clone());
    return spigot_mul(move(expintcfrac), spigot_exp(spigot_neg(move(x))));
}

static Spigot spigot_Ein(Spigot x)
{
    return spigot_monotone(make_shared<EinSeriesConstructor>(), x->clone());
}

static const UnaryFnWrapper expr_Ein("Ein", spigot_Ein);

/*
 * 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(move(Ein_minus_En), spigot_log(move(z)));
}

static const ConstantFnWrapper
expr_eulergamma{"eulergamma", spigot_eulergamma};

class EnHoleFiller : public HoleFiller {
    bigint n;

    virtual Spigot clone() override;
    virtual Spigot xspecial(int i) override;
    virtual Spigot yspecial(int i) override;
    virtual Spigot ygeneral(Spigot x) override;

  public:
    EnHoleFiller(const bigint &an, Spigot ax);
};

MAKE_CLASS_GREETER(EnHoleFiller);

EnHoleFiller::EnHoleFiller(const bigint &an, Spigot ax)
    : HoleFiller(move(ax)), n(an)
{
    dgreet(n);
}

Spigot EnHoleFiller::clone()
{
    return spigot_clone(this, n, x->clone());
}

Spigot EnHoleFiller::xspecial(int i)
{
    if (i == 0 && n > 1) return spigot_integer(0);
    return nullptr;
}

Spigot EnHoleFiller::yspecial(int i)
{
    if (i == 0 && n > 1) return spigot_rational(1, n-1);
    return nullptr;
}

Spigot EnHoleFiller::ygeneral(Spigot x)
{
    return spigot_En_internal(n, move(x));
}

static Spigot spigot_En(Spigot n, Spigot x)
{
    bigint nn, nd;
    if (!n->is_rational(&nn, &nd) || nd != 1)
        throw domain_error("first argument to En must be an integer");

    if (nn < 0)
        throw domain_error("first argument to En must be non-negative");

    if (nn > 1)
        x = spigot_enforce(move(x), ENFORCE_GE, spigot_integer(0),
                           domain_error("second argument to En must be "
                                        "non-negative"));
    else
        x = spigot_enforce(move(x), ENFORCE_GT, spigot_integer(0),
                           domain_error("second argument to En must be "
                                        "positive"));

    auto toret = make_unique<EnHoleFiller>(nn, move(x));
    if (auto replacement = toret->replace())
        return replacement;
    return move(toret);
}

static const BinaryFnWrapper expr_En("En", spigot_En);

static Spigot spigot_E1(Spigot x)
{
    x = spigot_enforce(move(x), ENFORCE_GT, spigot_integer(0),
                       domain_error("argument to E1 must be positive"));
    return spigot_En_internal(1, move(x));
}

static const UnaryFnWrapper expr_E1("E1", spigot_E1);

static 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(move(x)))),
                      move(Ein_term));
}

static const UnaryFnWrapper expr_Ei("Ei", spigot_Ei);

// Unusually lowercase class name, because li and Li really are
// different things :-)
class liHoleFiller : public HoleFiller {
    virtual Spigot clone() override;
    virtual Spigot xspecial(int i) override;
    virtual Spigot yspecial(int i) override;
    virtual Spigot ygeneral(Spigot x) override;

  public:
    liHoleFiller(Spigot ax);
};

MAKE_CLASS_GREETER(liHoleFiller);

liHoleFiller::liHoleFiller(Spigot ax) : HoleFiller(move(ax))
{
    dgreet();
}

Spigot liHoleFiller::clone()
{
    return spigot_clone(this, x->clone());
}

Spigot liHoleFiller::xspecial(int i)
{
    if (i == 0) return spigot_integer(0);
    return nullptr;
}

Spigot liHoleFiller::yspecial(int i)
{
    if (i == 0) return spigot_integer(0);
    return nullptr;
}

Spigot liHoleFiller::ygeneral(Spigot x)
{
    return spigot_Ei(spigot_log(move(x)));
}

static Spigot spigot_li(Spigot x)
{
    x = spigot_enforce(move(x), ENFORCE_GE, spigot_integer(0),
                       domain_error("argument to li must be non-negative"));
    auto toret = make_unique<liHoleFiller>(move(x));
    if (auto replacement = toret->replace())
        return replacement;
    return move(toret);
}

static const UnaryFnWrapper expr_li("li", spigot_li);

class LiHoleFiller : public HoleFiller {
    virtual Spigot clone() override;
    virtual Spigot xspecial(int i) override;
    virtual Spigot yspecial(int i) override;
    virtual Spigot ygeneral(Spigot x) override;

  public:
    LiHoleFiller(Spigot ax);
};

MAKE_CLASS_GREETER(LiHoleFiller);

LiHoleFiller::LiHoleFiller(Spigot ax) : HoleFiller(move(ax))
{
    dgreet();
}

Spigot LiHoleFiller::clone()
{
    return spigot_clone(this, x->clone());
}

Spigot LiHoleFiller::xspecial(int i)
{
    if (i == 0) return spigot_integer(0);
    if (i == 1) return spigot_integer(2);
    return nullptr;
}

Spigot LiHoleFiller::yspecial(int i)
{
    if (i == 0) return spigot_neg(spigot_li(spigot_integer(2)));
    if (i == 1) return spigot_integer(0);
    return nullptr;
}

Spigot LiHoleFiller::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(move(x))));
    Spigot Ein_log_2 = spigot_Ein(spigot_log(spigot_rational(1,2)));
    return spigot_add(move(log_log2_x), spigot_sub(move(Ein_log_2),
                                                   move(Ein_log_x)));
}

static Spigot spigot_Li(Spigot x)
{
    x = spigot_enforce(move(x), ENFORCE_GE, spigot_integer(0),
                       domain_error("argument to Li must be non-negative"));
    auto toret = make_unique<LiHoleFiller>(move(x));
    if (auto replacement = toret->replace())
        return replacement;
    return move(toret);
}

static const UnaryFnWrapper expr_Li("Li", spigot_Li);
