拉格朗日插值法

给定 \((t+1)\) 个不同的点,过这些点且最高次不大于 \(t\) 的多项式,有且只有一条。

本文将介绍如何使用拉格朗日插值法,求解这样的多项式。

方法

已知点 \((x_0, y_0)\)\((x_1, y_1)\)、...、\((x_t, y_t)\),拉格朗日插值法的思路是寻找多项式 \(l_j(x)\),使其在 \(x = x_j\) 时的取值为 1,在其他点的取值都是 0。

这样的多项式很好构造。

\[ l_j(x) = \frac{(x-x_0)...(x-x_{j-1})(x-x_{j+1})...(x-x_t)}{(x_j-x_0)...(x_j-x_{j-1})(x_j-x_{j+1})...(x_j-x_t)} \]

注意到,当 \(x=x_j\) 时,\(l_j(x_j)\) 的分子和分母相同,所以 \(l_j(x_j)=1\),当 \(x\) 取其他值时,其分子总是 0,所以结果为 0。

多项式 \(l_j(x)\) 就像开关一样,可以让 \(y_jl_j(x)\) 满足只有在 \(x = x_j\) 时的取值为 \(y_j\),而在其他点的取值都是 0。

基于此,可以继续构造多项式

\[ L(x) = y_0l_0(x) + y_1l_1(x) + ... + y_tl_t(x) \]

满足过已知的 \((t+1)\) 个点。

例子

给定点 \((x_0, y_0) = (1, 350)\)\((x_1, y_1) = (2, 770)\)\((x_2, y_2) = (3, 1350)\),求过这三个点的多项式。

先构造 \(l_j(x)\),有

\[ l_0(x) = \frac{(x - x_1)(x - x_2)}{(x_0 - x_1)(x_0 - x_2)} = \frac{(x-2)(x-3)}{(1-2)(1-3)} = \frac{1}{2}x^2 - \frac{5}{2}x + 3 \]

\[ l_1(x) = \frac{(x-x_0)(x-x_2)}{(x_1-x_0)(x_1-x_2)} = \frac{(x-1)(x-3)}{(2-1)(2-3)} = -x^2 + 4x - 3 \]

\[ l_2(x) = \frac{(x-x_0)(x-x_1)}{(x_2-x_0)(x_2-x_1)} = \frac{(x-1)(x-2)}{(3-1)(3-2)} = \frac{1}{2}x^2 - \frac{3}{2}x + 1 \]

再构造 \(L(x)\),有

\[ \begin{aligned} L(x) &= y_0l_0(x) + y_1l_1(x) + y_2l_2(x) \\[1em] &= 350(\frac{1}{2}x^2 - \frac{5}{2}x + 3) + 770(-x^2 + 4x - 3) + 1350(\frac{1}{2}x^2 - \frac{3}{2}x + 1) \\[1em] &= 80x^2 + 180x + 90 \end{aligned} \]

求解完毕。

证明

存在性

显而易见,我们总能按公式构造出 \(l_j(x)\),从而构造出 \(L(x)\)

注意到,\(l_j(x)\) 的分子是 \(t\) 个 1 次多项式相乘,分母是一个常数,所以 \(l_j(x)\) 是一个最高次不大于 \(t\) 的多项式。

任意个最高次不大于 \(t\) 的多项式相加,结果多项式的最高次也不会大于 \(t\),所以 \(L(x)\) 是一个最高次不大于 \(t\) 的多项式。

唯一性

假设这样的多项式不唯一,对任意两个最高次不大于 \(t\) 的拉格朗日多项式 \(L_1\)\(L_2\)

因为 \(L_1\)\(L_2\) 都过已知的 \((t+1)\) 个点,所以两者的差 \((L_1 - L_2)\) 在这 \((t+1)\) 个点的取值都是 0。

也就是说,\((L_1 - L_2)\) 一定是多项式 \((x-x_0)(x-x_1)...(x-x_t)\) 的倍数。

\(L_1 - L_2 \neq 0\),则其次数一定不小于 \((t+1)\)

而任意个最高次不大于 \(t\) 的多项式相减,结果多项式的最高次也不会大于 \(t\),所以 \((L_1 - L_2)\) 的最高次也不大于 \(t\)

矛盾。

所以 \(L_1 - L_2 = 0\),即 \(L_1 = L_2\)

代码

Polynomial 用来表示有限域 Secp256k1.n 上的多项式

\[ y \equiv a_0x^0 + a_1x^1 + ... a_tx^t \pmod{n} \]

并实现了多项式加法、乘法和求值等基本运算。

对于方法 interpolate_evaluate,我们的目标是计算插值得到的多项式在某点的取值,所以并不需要知道 \(L(x)\) 的具体形式,可以直接将参数 \(x\) 的值带入公式计算最终结果。方法的内层循环用来计算 \(l_i(x)\) 的分子和分母的值,外层循环将每个 \(y_il_i(x)\)(分子和分母)的值保存到数组 lagrange 中,最后对所有的 \(y_il_i(x)\) 通分并求和,得到 \(L(x)\) 的值。

def interpolate_evaluate(points: list, x: int) -> int:
    """Lagrange interpolate with the giving points, then evaluate y at x"""
    if len(points) < 2:
        raise ValueError('Lagrange interpolation requires at least 2 points')
    # [(numerator, denominator) ...]
    lagrange = [(0, 0)] * len(points)
    # the product of all the denominator
    denominator_product = 1
    for i in range(len(points)):
        numerator, denominator = 1, 1
        for j in range(len(points)):
            if j != i:
                numerator *= (x - points[j][0])
                denominator *= (points[i][0] - points[j][0])
        lagrange[i] = (points[i][1] * numerator, denominator)
        denominator_product *= denominator
    numerator_sum = 0
    for (numerator, denominator) in lagrange:
        numerator_sum += numerator * denominator_product // denominator
    return numerator_sum * modular_multiplicative_inverse(denominator_product, curve.n) % curve.n

参考