/*
 * inverter.cpp: bounded-interval adaptation of Newton-Raphson to
 * invert a function f.
 *
 * This adapts the general idea of N-R so that instead of returning a
 * mere sequence of real numbers converging to x = f^{-1}(y), it
 * returns a sequence of successively smaller intervals that contain
 * x.
 *
 * The key point is that the client, instead of merely providing a way
 * to compute the derivative f' at a point, must provide a way to
 * compute its largest value over a whole interval. Also, for
 * simplicity, we require that the function must be monotonic.
 *
 * (That isn't expected to be a big problem, because surely you needed
 * that anyway in order to guarantee a uniquely defined answer. Any
 * function you have an interest in inverting in the first place will
 * surely at least be _piecewise_ monotonic, and if there's more than
 * one piece, then there will be some sensible means of stating in
 * advance which piece you want, e.g. the explicitly separated names
 * for the positive and negative branches of the Lambert W function.)
 *
 * So, suppose you know that your target x lies somewhere in the
 * interval [a,b], and you can compute f at the two endpoints. So you
 * have a setup something like this, where the shape of f is unknown
 * to you except that you know its slope is always between 0 and some
 * maximum value m:
 *
 *                                              * (b, f(b))
 *                                        ,----'
 *                                      ,'
 *                                 ,---'
 *   -----------------------------/---------------------- target value y
 *                              ,'
 *                             /  ^
 *                         ,--'   |
 *                        /       |
 *                      ,'       the x we want to end up returning
 *        (a, f(a)) *--'
 *
 * Then you can draw lines from _both_ endpoints of that curve with
 * the same maximum slope, and find where they meet the target
 * y-coordinate ...
 *
 *                                              * (b, f(b))
 *                                        ,----/
 *                          /           ,'    /
 *                         /       ,---'     /
 *   ---------------------A-------/---------B------------
 *                       /      ,'         /
 *                      /      /          /
 *                     /   ,--'
 *                    /   /
 *                   /  ,'
 *        (a, f(a)) *--'
 *
 * ... and then we know that the new interval [A,B] must also bound
 * the value x that we're after.
 *
 * Just like Newton-Raphson proper, this technique converges faster
 * and faster, because the limiting factor is the difference between
 * the slope given by max f', and the slope of the line that just
 * connects the two points (a,f(a)) and (b,f(b)). In the above
 * diagram, that difference is quite large: the example function is
 * able to wiggle back and forth quite a bit, and spend a lot of time
 * going horizontally, and still be able to hit the other endpoint
 * without having to violate its derivative bound. But as you zoom in
 * on the part of the function you're after, f' will vary less and
 * less (at least, assuming it's continuous), so the function will be
 * constrained much more to a very long and thin parallelogram between
 * those two points, which will intersect the target horizontal line
 * in a very small interval compared to the original one:
 *
 *                            +--* (b, f(b))
 *                           /  /
 *                          /  /
 *                         /  /
 *   ---------------------A--B------------
 *                       /  /
 *                      /  /   f must be contained in
 *                     /  /    this parallelogram
 *                    /  /
 *                   /  /      new interval [A,B] far smaller
 *        (a, f(a)) *--+       than previous [a,b]
 *
 * The actual formula you use to get from one of the initial interval
 * endpoints to the new interval endpoint is the standard Newton-
 * Raphson formula, only with f'(a) replaced by max f' over the
 * interval:
 *
 *     A = a - f(a) / (max f')
 *     B = b - f(b) / (max f')
 *
 * and, conveniently, this still works even if f is decreasing rather
 * than increasing (in which case that the 'maximum' of f' is taken to
 * mean the most negative value it takes).
 */

#include <stdlib.h>

#include "spigot.h"
#include "funcs.h"
#include "cr.h"
#include "error.h"

class NewtonInverter : public BinaryIntervalSource {
    shared_ptr<InverseConstructor> fcons;
    bigint nlo, nhi;
    unsigned dbits;
    Spigot y;

    int crState { -1 };
    bigint nlo_min, nhi_min, nlo_max, nhi_max;
    unsigned dbits_min, dbits_max;

    unique_ptr<BracketingGenerator> bgmin, bgmax;

    virtual Spigot clone() override;
    virtual void gen_bin_interval(bigint *ret_lo, bigint *ret_hi,
                                  unsigned *ret_bits) override;

    void make_new_bounds();

  public:
    NewtonInverter(shared_ptr<InverseConstructor> afcons,
                   bigint anlo, bigint anhi, unsigned adbits, Spigot ay);
};

MAKE_CLASS_GREETER(NewtonInverter);

NewtonInverter::NewtonInverter(shared_ptr<InverseConstructor> afcons,
                               bigint anlo, bigint anhi, unsigned adbits,
                               Spigot ay)
    : fcons(afcons), nlo(anlo), nhi(anhi), dbits(adbits), y(move(ay))
{
    dgreet();
}

Spigot NewtonInverter::clone()
{
    return spigot_clone(this, fcons, nlo, nhi, dbits, y->clone());
}

void NewtonInverter::make_new_bounds()
{
    /*
     * Subroutine to fetch the max value of f' for our current
     * interval, and apply the Newton-Raphson (ish) formula to
     * construct two spigots for the new interval bounds.
     */

    dprint("get derivative bound for interval (",
           nlo, ",", nhi, ") / 2^", dbits);

    Spigot dmax = fcons->fprime_max(nlo, nhi, dbits);

    bigint d = bigint_power(2, dbits);

    bgmin = make_unique<BracketingGenerator>(
        spigot_add(spigot_rational(nlo, d),
                   spigot_div(spigot_sub(y->clone(), fcons->f(nlo, dbits)),
                              dmax->clone())));

    bgmax = make_unique<BracketingGenerator>(
        spigot_add(spigot_rational(nhi, d),
                   spigot_div(spigot_sub(y->clone(), fcons->f(nhi, dbits)),
                              move(dmax))));
}

void NewtonInverter::gen_bin_interval(bigint *ret_lo, bigint *ret_hi,
                                      unsigned *ret_bits)
{
    crBegin;

    /*
     * This outer loop has one iteration per bounding interval.
     */
    while (true) {
        /*
         * Make a pair of spigots describing the ideal values of the
         * next interval bounds.
         */
        make_new_bounds();

        /*
         * This inner loop gets a rational approximation to each of
         * the new bounds, and iterates on those rationals until they
         * have actually managed to narrow the bounding interval
         * usefully. So executing 'continue' inside this loop means
         * 'we haven't squeezed all the juice out of this pair of
         * spigots yet, go back round the inner loop', whereas 'break'
         * means 'these spigots are done with, go back round the outer
         * loop and get a fresh pair for the new bounding interval'.
         */
        while (true) {
            /*
             * Get raw rational approximations to the new interval
             * bounds.
             */
            bgmin->get_bracket_shift(&nlo_min, &nhi_min, &dbits_min);
            bgmax->get_bracket_shift(&nlo_max, &nhi_max, &dbits_max);
            dprint("min output bracket (", nlo_min, ",", nhi_min,
                   ") / 2^",dbits_min);
            dprint("max output bracket (", nlo_max, ",", nhi_max,
                   ") / 2^",dbits_max);

            /*
             * Put them, and the previous interval bounds, over a
             * common denominator.
             */
            dbits = match_dyadic_rationals(nlo, dbits,
                                           nhi, dbits,
                                           nlo_min, dbits_min,
                                           nhi_min, dbits_min,
                                           nlo_max, dbits_max,
                                           nhi_max, dbits_max);
            dprint("evened output brackets (", nlo_min, ",", nhi_min,
                   "),(", nlo_max, ",", nhi_max, ") / 2^", dbits);

            /*
             * Sanity check.
             */
            if (nlo_min > nhi_max)
                internal_fault("interval bounds misordered");

            /*
             * Retry the inner loop early if we've made no progress
             * whatsoever.
             *
             * In principle, the spigots held by bgmin and bgmax
             * should always contain real numbers bounding a strict
             * subinterval of the interval we last returned to the
             * client. But our current _approximations_ to those real
             * numbers may not yet do the same.
             *
             * So, if we haven't even managed to make the interval
             * smaller at all, there's no point even returning
             * anything to the client yet, and it _should_ always be
             * possible to correct the problem by just iterating on
             * bgmin and bgmax again, and holding out for a tighter
             * approximation to the true bounding interval, which
             * should eventually end up strictly smaller than the one
             * we last returned.
             */
            if (nlo_min <= nlo && nhi_max >= nhi) {
                dprint("no part of previous interval ruled out yet");
                continue;
            }

            /*
             * Intersect the new interval with the old one.
             *
             * Now we've got through the above check, _one_ of nlo_min
             * and nhi_max has to be strictly within the bounds of the
             * previous interval. But the other one might still not
             * be. So we should be careful to avoid accepting a looser
             * bound on either end of the interval than we previously
             * had.
             *
             * (If we did, then our base class BinaryIntervalSource
             * would normalise the detail away and the _client_
             * wouldn't see our output interval expanding at either
             * end. But the big problem would come when we used the
             * expanded interval as the basis for the next iteration
             * of the outer loop, and asked for bounds on f' over a
             * larger interval than necessary. That would at least
             * slow down convergence, and perhaps prevent it entirely
             * if the result was to make the same thing happen even
             * more in the next iteration...)
             */
            intersect_intervals(nlo, nhi, nlo_min, nhi_max);
            dprint("intersected output bracket (",
                   nlo, ",", nhi, ") / 2^", dbits);

            /*
             * Now we have a new bounding interval that it's worth
             * returning to the client.
             */
            *ret_lo = nlo;
            *ret_hi = nhi;
            *ret_bits = dbits;

            dprint("returning (", nlo, ",", nhi, ") / 2^", dbits);

            crReturnV;

            /*
             * Decide whether we should go back round the outer loop
             * next, or just the inner one.
             */
            if (nhi_min < nlo_max) {
                /*
                 * The combined widths of the original output
                 * intervals are smaller than the overall interval
                 * we're returning (even after accounting for a
                 * rounding error of 1 in each one).
                 *
                 * In other words: the uncertainty in the input
                 * interval is bigger than the uncertainty in the
                 * values of f at the endpoints, and hence, is the
                 * larger factor affecting the size of the interval
                 * we're outputting.
                 *
                 * So this seems like a good moment to give up on this
                 * input interval, and go back and fetch a narrower
                 * one.
                 */
                dprint("going back round outer loop");
                break;
            }
        }
    }

    crEnd;
}

Spigot spigot_invert(shared_ptr<InverseConstructor> f,
                     bigint n1, bigint n2, unsigned dbits, Spigot y)
{
    return make_unique<NewtonInverter>(move(f), n1, n2, dbits, move(y));
}
