#!/usr/bin/env python3

import sys
import argparse
import spigot

# Use the mediant method to find the simplest fraction (that is, with
# the smallest denominator) in a given interval.
#
# Continued fractions are the tool for the job if you want a stream of
# better and better approximations to a single target number; but if
# you just want to land anywhere in a given _interval_, then mediants
# are a simpler approach.
#
# The idea is to start with a pair of fractions a/A and b/B; there's a
# theorem that says that the mediant (a+b)/(A+B) has the smallest
# denominator out of all fractions strictly within that interval,
# provided that Ab-aB = 1. Moreover, if Ab-aB = 1, then replacing
# either of the endpoint rationals with the resulting mediant gives
# you another pair of rationals with the same property.
#
# So, start off with a pair of fractions n/1 and (n+1)/1 representing
# adjacent integers, which certainly do satisfy the Ab-aB=1 property;
# then binary-search that range by repeatedly computing the mediant,
# until you find the first fraction that lands in the target interval.
# At every step, you have three cases: either the mediant is to the
# left of your whole target interval (so replace the lower bound of
# your search range), or to the right (so replace the upper bound), or
# it's in the target interval, in which case, stop.
#
# The results tend to look suspiciously like one of the continued
# fraction convergents to the interval endpoints. So in many cases you
# can solve this problem by just generating the convergents of both
# numbers and trying them until one works. But one feature of this
# algorithm is that it adapts readily to _open_ intervals, i.e.
# returns the simplest rational q such that a < q < b, even if a or b
# or both is itself a rational.

def mediant_step(lo, hi, a, A, b, B, accelerate, debug):
    # Consistency check. This also enforces, all in one go, that a/A
    # and b/B are in lowest terms (because we show for each pair that
    # an integer linear combination of them has magnitude 1).
    assert A*b-a*B == 1

    if debug:
        debug("bounds [{:d}/{:d}, {:d}/{:d}]".format(a,A,b,B))

    # Find the mediant between a/A and b/B.
    c, C = a+b, A+B
    cspig = spigot.fraction(c,C)
    if debug:
        debug(" -> {:d}/{:d}".format(c,C))

    # Compare that value against the endpoints of our target interval.
    if cspig >= hi:
        # c/C is still too large, so replace the upper bound of our
        # interval.
        #
        # However, we don't just replace it with c/C. If c/C is a
        # _lot_ too large, then we'll spend a lot of steps heading in
        # the same direction, which means we'll run through
        # (a+b)/(A+B), (a+2b)/(A+2B), ..., (a+kb)/(A+kB), ... until we
        # find a value within range. This is slow and tedious, and
        # we'd rather shortcut it by solving an equation to tell us
        # the right value of k immediately.
        #
        # Our problem at the moment is that (a+b)/(A+B) >= hi; to
        # speed things up, we'll find the largest k such that
        # (ka+b)/(kA+B) < hi, and do that many steps in one go. To
        # find k, we turn the < in that inequality into an =, and
        # solve the equation exactly to find the point where the LHS
        # would cross over the RHS. Then we stop k just short of
        # there.
        if debug:
            debug(" too large")
        if accelerate:
            k = int(spigot.eval(
                "floor( (B hi - b) / (a - A hi) )",
                {"a":a, "A":A, "b":b, "B":B, "lo":lo, "hi":hi}))
            if debug:
                debug(" -> {:d} steps".format(k))
        else:
            k = 1
        b, B = k*a+b, k*A+B
        if debug:
            debug(" -> new upper bound {:d}/{:d}\n".format(b,B))
    elif cspig <= lo:
        # c/C is still too small, so replace the lower bound of our
        # interval. We do the same acceleration trick, but the other
        # way round, solving (a+kb)/(A+kB)=lo.
        if debug:
            debug(" too small")
        if accelerate:
            k = int(spigot.eval(
                "floor( (A lo - a) / (b - B lo) )",
                {"a":a, "A":A, "b":b, "B":B, "lo":lo, "hi":hi}))
            if debug:
                debug(" -> {:d} steps".format(k))
        else:
            k = 1
        a, A = a+k*b, A+k*B
        if debug:
            debug(" -> new lower bound {:d}/{:d}\n".format(a,A))
    else:
        # c/C is in range, so return an interval narrowed to just that
        # point, and set the flag that says we're done.
        if debug:
            debug(" just right\n")
        return (True, c, C, c, C)

    # Re-check consistency on output.
    assert A*b-a*B == 1
    return False, a, A, b, B

def mediant_algorithm(lo, hi, accelerate, debug):
    sign = +1

    if lo > hi:
        # Make sure our interval is the right way round.
        if debug:
            debug("swapped arguments\n")
        lo, hi = hi, lo

    if lo < 0:
        if hi <= 0:
            # If the interval is between two negative numbers, it's
            # easiest to just negate them so that we're working with a
            # positive interval, and remember to negate our final answer
            # on output.
            lo, hi = -hi, -lo
            sign = -1
            if debug:
                debug("flipped sign\n")
        else:
            # Single silliest special case: if the interval includes zero,
            # just emit zero.
            if debug:
                debug("special case: zero in interval\n")
            return sign*0, 1

    if debug:
        debug("normalised interval: ({},{})\n".format(lo, hi))

    # Find the two integers that bound our interval.
    a = int(spigot.floor(lo))
    b = int(spigot.ceil(hi))
    assert a < b
    if a + 1 != b:
        # Second silliest special case: if the interval includes at least
        # one integer, emit the integer of smallest magnitude in it.
        if debug:
            debug("special case: integer in interval\n")
        return sign*(a + 1), 1

    # The integers a,b are our starting values, so introduce variables
    # to hold their denominators, which start off at 1.
    A = B = 1

    while True:
        done, a, A, b, B = mediant_step(lo, hi, a, A, b, B,
                                        accelerate, debug)
        if done:
            return a, A

def main():
    parser = argparse.ArgumentParser(
        description='Find the simplest rational between two real numbers.')
    parser.add_argument("bound", nargs=2)
    parser.add_argument("--no-accelerate", action="store_true", help=
                        "Turn off acceleration in the search algorithm.")
    parser.add_argument("--debug", "-d", action="store_true", help=
                        "Print diagnostics as the algorithm progresses.")
    args = parser.parse_args()

    lo = spigot.eval(args.bound[0])
    hi = spigot.eval(args.bound[1])
    debug = sys.stdout.write if args.debug else None

    c, C = mediant_algorithm(lo, hi, not args.no_accelerate, debug)

    formatted = "{}".format(c)
    if C != 1:
        formatted += "/{}".format(C)
    sys.stdout.write(formatted + "\n")

if __name__ == '__main__':
    main()
