''' mbinary
#########################################################################
# File : interplotion.py
# Author: mbinary
# Mail: zhuheqin1@gmail.com
# Blog: https://mbinary.xyz
# Github: https://github.com/mbinary
# Created Time: 2018-10-02  21:14
# Description:
#########################################################################
'''

#########################################################################
# File : interplotion.py
# Author: mbinary
# Mail: zhuheqin1@gmail.com
# Blog: https://mbinary.xyz
# Github: https://github.com/mbinary
# Created Time: 2018-05-18  09:29
# Description: 插值计算,有牛顿插值,拉格朗日插值,以及通过插值得到的多项式估计新的函数值
#########################################################################


import sympy
from collections import namedtuple
from functools import reduce
from operator import mul

X = sympy.Symbol('x')
point = namedtuple('point', ['x', 'y'])


class interplotion:
    def __init__(self, points):
        self.points = [point(x, y) for x, y in points]
        self.xs = [i for i, j in points]
        self.poly, self.rem = self.newton(self.points, 0, len(self.points)-1)

    def newton(self, li, a, b):
        '''li:[(x,f(x))...]'''

        qs = [li[0].y]

        def quoDiff(begin, end):
            if begin == end:
                return li[begin].y
            q = (quoDiff(begin+1, end)-quoDiff(begin, end-1)) / \
                (li[end].x-li[begin].x)
            if begin == a:
                qs.append(q)
            return q

        quoDiff(a, b)
        poly, base = 0, 1
        for i, q in enumerate(qs):
            poly += q*base
            base *= X-li[i].x
        return poly, base*qs[-1]

    def lagrange(self, points=None):
        xs = None
        if points is None:
            xs = self.xs
            points = self.points
        else:
            xs = [x for x, y in points]
        product = reduce(mul, [X-x for x in xs], 1)
        poly = 0
        for x, y in points:
            tmp = product/(X-x)
            coef = y/(tmp.subs(X, x))
            poly += coef * tmp
        return poly

    def predict(self, val, poly=None):
        if poly is None:
            poly = self.poly
        return poly.subs(X, val)  # note the func  subs


if __name__ == '__main__':
    f = interplotion([(81, 9), (100, 10), (121, 11)])
    p = f.lagrange()
    print(p.subs(X, 105))
    print(p)

    intor = interplotion([(0, 11), (0.02, 9), (0.04, 7), (0.06, 10)])
    p = intor.lagrange()
    print(p)
    res = intor.predict(0.08)
    print(res)
