#!/usr/bin/env python3

# Some simple demos of the spigot Python bindings.

import sys
import itertools
import math
import spigot

# ----------------------------------------------------------------------
# spigot values and floats can both be formatted using the standard
# Python string.format system, and if you ask for more digits than
# IEEE double precision can represent, you can see the difference.

sys.stdout.write("""\
Real pi: {:.50f}
IEEE pi: {:.50f}
""".format(spigot.pi, float(spigot.pi)))

# ----------------------------------------------------------------------
# spigot.eval parses the same expression syntax that the spigot binary
# accepts on its command line.

expr = "sin(exp(apery))"
sys.stdout.write("{} = {}\n".format(expr, spigot.eval(expr)))

# ----------------------------------------------------------------------
# By adding an optional second parameter containing variable
# definitions, spigot.eval can also evaluate an expression
# parametrised by values you set differently in each parse.

expr = "sin(x+1)"
sys.stdout.write("""\
With f(x) = {}:
  f(1) = {}
  f(2) = {}
""".format(expr, spigot.eval(expr, {'x':1}), spigot.eval(expr, {'x':2})))

# ----------------------------------------------------------------------
# spigot objects have various methods defined on them, such as sign()
# which lets you tell whether a real number is positive or negative -
# and unlike floats, applying this to the result of a complex
# expression shouldn't get the wrong answer.

testexpr = "pi^e - exp(pi)"
sys.stdout.write("Sign of {} = {}\n".format(
    testexpr, spigot.eval(testexpr).sign_int()))

# ----------------------------------------------------------------------
# tan(10^100) is hard because you have to reduce 10^100 mod pi, for
# which you have to know pi to over 100 places (obviously) and 10^100
# to the same precision (which, less obviously, IEEE floats have
# trouble with). Here we see spigot's own expression evaluator
# interpreting '10^100', and getting the same (right) answer when it
# gets its input from a Python large integer.
#
# Just to show the difference, we then show one way of getting the
# _wrong_ answer, by feeding spigot.tan the closest IEEE float
# representation of 10^100 instead of a number exactly equal to
# 10^100. But the computation of the tan function is still exact, so
# that value differs in turn from what you would get from the standard
# math.tan applied to the same floating value - the standard glibc tan
# will get that one _approximately_ right (proving that it's managed
# to reduce the input value it was given correctly) but spigot will
# still provide more digits of precision of that 'wrong' answer.

sys.stdout.write("""\
tan of spigot's  10^100 = {: .40f}
tan of Python's 10**100 = {: .40f}
tan of IEEE    1.0e+100 = {: .40f}
math.tan of IEEE 1e+100 = {: .40f}
""".format(spigot.eval("tan(10^100)"),
           spigot.tan(10**100),
           spigot.tan(1.0e+100),
           math.tan(1.0e+100)))

# ----------------------------------------------------------------------
# Spigot objects can return the decimal digits of the number they
# represent, in the form of a Python iterator which potentially
# yields an unlimited number of values, and which you can feed to all
# the usual tools such as itertools.islice.

sys.stdout.write("Decimal digits of e: {}\n".format(
    ",".join(["{:d}".format(n) for n in
              itertools.islice(spigot.e.to_digits(), 0, 25)] +
             ["..."])))

# You can also specify a different base, including enormous ones that
# don't fit in a machine integer...
sys.stdout.write("Base 10^20 digits of e: {}\n".format(
    ",".join(["{:d}".format(n) for n in
              itertools.islice(spigot.e.to_digits(10**20), 0, 2)] +
             ["..."])))

# ... and even vary the base _between digits_. Here we retrieve the
# value of e in a 'factorial base', in which the first digit after the
# point is interpreted in base 2, the next in base 3, then 4, and so
# on. In this system the expansion of e is trivial, and is 2.11111...
e_digits = spigot.e.to_digits()
sys.stdout.write("Factorial-base digits of e: {}\n".format(
    ",".join(["{:d}".format(e_digits.get_digit(i)) for i in range(1,25)] +
             ["..."])))

# ----------------------------------------------------------------------
# Spigot objects can return the continued fraction of the real number
# they represent, again in the form of a Python iterator. The
# convergent rationals can be returned in the same way.

sys.stdout.write("Continued fraction of e: {}\n".format(
    ",".join(["{:d}".format(n) for n in
              itertools.islice(spigot.e.to_cfrac(), 0, 23)] +
             ["..."])))

sys.stdout.write("Convergents of pi: {}\n".format(
    ",".join(["{:d}/{:d}".format(n,d) for (n,d) in
              itertools.islice(spigot.pi.to_convergents(), 0, 6)] +
             ["..."])))

# ----------------------------------------------------------------------
# You can also construct a spigot object _from_ a sequence of
# continued fraction terms, by providing those terms either as an
# iterator or as a function returning the nth term.

sys.stdout.write("""\
Continued fraction 0;1,2,3,4,5,6,... (https://oeis.org/A052119):
  lambda n: n     -> {:.50}
  itertools.count -> {:.50}
""".format(
    spigot.from_cfrac(lambda n: n),
    spigot.from_cfrac(itertools.count(0))))

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

sys.stdout.write("""\
Continued fraction 0;1,1,2,3,5,8,... (https://oeis.org/A073822):
  generator       -> {:.50}
""".format(spigot.from_cfrac(fibonacci())))

# ----------------------------------------------------------------------
# In a similar way, you can construct a spigot from a sequence of
# digits in a given number base, provided in the form of an iterator.

def thue_morse_binary_digits():
    """The Thue-Morse constant is defined as the binary fraction in which
    the digit with place value 2^-(n+1) is 1 if and only if the binary
    representation of n has an odd number of 1 bits."""
    yield 0 # integer part
    for n in itertools.count():
        parity = n
        while parity > 1:
            parity = (parity & 1) ^ (parity >> 1)
        yield parity

sys.stdout.write(
    "Thue-Morse constant (http://oeis.org/A014571) =\n  {:.70}\n".format(
        spigot.from_digits(thue_morse_binary_digits(), base=2)))

# You can also _change_ the base part way through delivering your
# number.

def champernowne_digits():
    """The Champernowne constant is defined as the decimal expansion in
    which the integers from 1 upwards are written in decimal after the
    point, concatenated to give 0.1234567891011121314..."""
    base = 10
    yield 0 # integer part
    for n in itertools.count(1):
        if n == base:
            # When we move up from 1-digit to 2-digit integers, we're
            # delivering two decimal digits at a time, which is
            # equivalent to delivering a larger 'digit' in base 100.
            # When we get to 3-digit integers, we change base again to
            # 1000, and so on.
            base *= 10
        yield spigot.BASE_DIGIT(base, n)

champernowne_cfrac = spigot.from_digits(champernowne_digits()).to_cfrac()

sys.stdout.write("""\
Continued fraction of Champernowne constant (https://oeis.org/A030167):
  {}, then a {}-digit number
""".format(
    ",".join("{}".format(next(champernowne_cfrac)) for _ in range(18)),
    len("{}".format(next(champernowne_cfrac)))))
