|
| 1 | +""" |
| 2 | +Lagrange Interpolation |
| 3 | +
|
| 4 | +Given n+1 data points (x0, y0), (x1, y1), ..., (xn, yn) with distinct x-values, |
| 5 | +the Lagrange interpolating polynomial is the unique polynomial of degree <= n that |
| 6 | +passes through all given points. |
| 7 | +
|
| 8 | +For each i, the basis polynomial is: |
| 9 | + L_i(x) = product_{j != i} (x - x_j) / (x_i - x_j) |
| 10 | +
|
| 11 | +The interpolating polynomial is: |
| 12 | + P(x) = sum_i y_i * L_i(x) |
| 13 | +
|
| 14 | +Unlike Newton's forward-difference formula, Lagrange interpolation works for |
| 15 | +arbitrarily spaced data points. |
| 16 | +
|
| 17 | +References: |
| 18 | + - https://en.wikipedia.org/wiki/Lagrange_polynomial |
| 19 | + - https://mathworld.wolfram.com/LagrangeInterpolatingPolynomial.html |
| 20 | +""" |
| 21 | + |
| 22 | +from __future__ import annotations |
| 23 | + |
| 24 | + |
| 25 | +def lagrange_interpolation(x_points: list[float], y_points: list[float], x: float) -> float: |
| 26 | + """ |
| 27 | + Estimate f(x) using the Lagrange interpolating polynomial through the |
| 28 | + given data points (x_points[i], y_points[i]). |
| 29 | +
|
| 30 | + :param x_points: List of distinct x-coordinates of the data points. |
| 31 | + :param y_points: List of y-coordinates corresponding to x_points. |
| 32 | + :param x: The x-value at which to evaluate the interpolating polynomial. |
| 33 | + :return: The interpolated value P(x). |
| 34 | + :raises ValueError: If x_points and y_points have different lengths, |
| 35 | + if fewer than 2 points are given, or if x_points |
| 36 | + contains duplicate values. |
| 37 | +
|
| 38 | + Examples — interpolating through known points of f(x) = x^2: |
| 39 | + >>> lagrange_interpolation([1, 2, 3], [1, 4, 9], 2.5) |
| 40 | + 6.25 |
| 41 | + >>> lagrange_interpolation([1, 2, 3], [1, 4, 9], 1.5) |
| 42 | + 2.25 |
| 43 | + >>> lagrange_interpolation([1, 2, 3], [1, 4, 9], 3.0) |
| 44 | + 9.0 |
| 45 | +
|
| 46 | + Two-point linear interpolation: |
| 47 | + >>> lagrange_interpolation([0, 1], [0, 1], 0.5) |
| 48 | + 0.5 |
| 49 | + >>> lagrange_interpolation([0, 2], [0, 4], 1.0) |
| 50 | + 2.0 |
| 51 | +
|
| 52 | + Four points from f(x) = x^3, interpolated at a non-grid point: |
| 53 | + >>> lagrange_interpolation([0, 1, 2, 3], [0, 1, 8, 27], 1.5) |
| 54 | + 3.375 |
| 55 | +
|
| 56 | + Error cases: |
| 57 | + >>> lagrange_interpolation([1, 2], [1], 1.5) |
| 58 | + Traceback (most recent call last): |
| 59 | + ... |
| 60 | + ValueError: x_points and y_points must have the same length |
| 61 | + >>> lagrange_interpolation([1], [1], 1.0) |
| 62 | + Traceback (most recent call last): |
| 63 | + ... |
| 64 | + ValueError: at least 2 data points are required |
| 65 | + >>> lagrange_interpolation([1, 1, 3], [1, 2, 3], 2.0) |
| 66 | + Traceback (most recent call last): |
| 67 | + ... |
| 68 | + ValueError: x_points must be distinct (duplicate found: 1) |
| 69 | + """ |
| 70 | + if len(x_points) != len(y_points): |
| 71 | + raise ValueError("x_points and y_points must have the same length") |
| 72 | + n = len(x_points) |
| 73 | + if n < 2: |
| 74 | + raise ValueError("at least 2 data points are required") |
| 75 | + seen: set[float] = set() |
| 76 | + for val in x_points: |
| 77 | + if val in seen: |
| 78 | + raise ValueError(f"x_points must be distinct (duplicate found: {val})") |
| 79 | + seen.add(val) |
| 80 | + |
| 81 | + result = 0.0 |
| 82 | + for i in range(n): |
| 83 | + basis = 1.0 |
| 84 | + for j in range(n): |
| 85 | + if j != i: |
| 86 | + basis *= (x - x_points[j]) / (x_points[i] - x_points[j]) |
| 87 | + result += y_points[i] * basis |
| 88 | + return result |
| 89 | + |
| 90 | + |
| 91 | +if __name__ == "__main__": |
| 92 | + import doctest |
| 93 | + |
| 94 | + doctest.testmod() |
0 commit comments