Monotone cubic interpolation

From testwiki
Jump to navigation Jump to search

Template:Short description In the mathematical field of numerical analysis, monotone cubic interpolation is a variant of cubic interpolation that preserves monotonicity of the data set being interpolated.

Monotonicity is preserved by linear interpolation but not guaranteed by cubic interpolation.

Monotone cubic Hermite interpolation

Example showing non-monotone cubic interpolation (in red) and monotone cubic interpolation (in blue) of a monotone data set.

Monotone interpolation can be accomplished using cubic Hermite spline with the tangents mi modified to ensure the monotonicity of the resulting Hermite spline.

An algorithm is also available for monotone quintic Hermite interpolation.

Interpolant selection

There are several ways of selecting interpolating tangents for each data point. This section will outline the use of the Fritsch–Carlson method. Note that only one pass of the algorithm is required.

Let the data points be (xk,yk) indexed in sorted order for k=1,…n.

  1. Compute the slopes of the secant lines between successive points:

    δk=yk+1−ykxk+1−xk

    for k=1,…n−1.

  2. These assignments are provisional, and may be superseded in the remaining steps. Initialize the tangents at every interior data point as the average of the secants,

    mk=δk−1+δk2

    for k=2,…n−1.

    For the endpoints, use one-sided differences:

    m1=δ1 and mn=δn−1.

    If δk−1 and δk have opposite signs, set mk=0.

  3. For k=1,…n−1, where ever δk=0 (where ever two successive yk=yk+1 are equal),
    set mk=mk+1=0, as the spline connecting these points must be flat to preserve monotonicity.
    Ignore steps 4 and 5 for those k.

  4. Let

    αk=mk/δk and βk=mk+1/δk.

    If either αk or βk is negative, then the input data points are not strictly monotone, and (xk,yk) is a local extremum. In such cases, piecewise monotone curves can still be generated by choosing mk=0 if αk<0 or mk+1=0 if βk<0, although strict monotonicity is not possible globally.

  5. To prevent overshoot and ensure monotonicity, at least one of the following three conditions must be met:
(a) the function

ϕk=αk−(2αk+βk−3)23(αk+βk−2)>0, or

(b) αk+2βk−3≤0, or
(c) 2αk+βk−3≤0.
Only condition (a) is sufficient to ensure strict monotonicity: ϕk must be positive.

One simple way to satisfy this constraint is to restrict the vector (αk,βk) to a circle of radius 3. That is, if αk2+βk2>9, then set

τk=3αk2+βk2,

and rescale the tangents via

mk=τkαkδk and mk+1=τkβkδk.

Alternatively it is sufficient to restrict αk≤3 and βk≤3. To accomplish this, if αk>3, then set mk=3δk, and if βk>3, then set mk+1=3δk.

Cubic interpolation

After the preprocessing above, evaluation of the interpolated spline is equivalent to a cubic Hermite spline, using the data xk, yk, and mk for k=1,…n.

To evaluate at x, find the index k in the sequence where x, lies between xk, and xk+1, that is: xk≤x≤xk+1. Calculate

Δ=xk+1−xk and t=x−xkΔ

then the interpolated value is

finterpolated(x)=yk⋅h00(t)+Δ⋅mk⋅h10(t)+yk+1⋅h01(t)+Δ⋅mk+1⋅h11(t)

where hii are the basis functions for the cubic Hermite spline.

Example implementation

The following Python implementation takes a data set and produces a monotone cubic spline interpolant function:

"""
Monotone Cubic Spline Interpolation.
"""

def create_interpolant(xs, ys):
    n = len(xs)
    if n != len(ys):
        raise ValueError("xs and ys must have the same length.")
    if n == 0:
        return lambda x: (0.0, 0.0)
    if n == 1:
        value = float(ys[0])
        return lambda x: (value, 0.0)

    # Sort xs and ys together
    sorted_pairs = sorted(zip(xs, ys), key=lambda p: p[0])
    xs = [float(x) for x, _ in sorted_pairs]
    ys = [float(y) for _, y in sorted_pairs]

    # Compute consecutive differences and slopes
    dxs = [xs[i + 1] - xs[i] for i in range(n - 1)]
    dys = [ys[i + 1] - ys[i] for i in range(n - 1)]
    ms = [dy / dx for dx, dy in zip(dxs, dys)]

    # Compute first-degree coefficients (c1s)
    c1s = [ms[0]]
    for i in range(len(ms) - 1):
        m, m_next = ms[i], ms[i + 1]
        if m * m_next <= 0:
            c1s.append(0.0)
        else:
            dx, dx_next = dxs[i], dxs[i + 1]
            common = dx + dx_next
            c1s.append(
                3 * common / ((common + dx_next) / m + (common + dx) / m_next)
            )
    c1s.append(ms[-1])

    # Compute second- and third-degree coefficients (c2s, c3s)
    c2s, c3s = [], []
    for i in range(len(c1s) - 1):
        c1, m = c1s[i], ms[i]
        inv_dx = 1 / dxs[i]
        common = c1 + c1s[i + 1] - 2 * m
        c2s.append((m - c1 - common) * inv_dx)
        c3s.append(common * inv_dx * inv_dx)

    def interpolant(x):
        # Clamp x to range
        if x <= xs[0]:
            i = 0
        elif x >= xs[-1]:
            i = n - 2
        else:
            # Binary search for interval
            low, high = 0, n - 2
            while low <= high:
                mid = (low + high) // 2
                if xs[mid] < x:
                    low = mid + 1
                else:
                    high = mid - 1
            i = max(0, high)

        dx = x - xs[i]
        val = ys[i] + dx * (c1s[i] + dx * (c2s[i] + dx * c3s[i]))
        dval = c1s[i] + dx * (2 * c2s[i] + dx * 3 * c3s[i])
        return val, dval

    return interpolant


# Example usage
if __name__ == "__main__":
    X = [0, 1, 2, 3, 4]
    Y = [0, 1, 4, 9, 16]
    spline = create_interpolant(X, Y)

    print("# Data")
    print("x\tf(x)")
    for x, y in zip(X, Y):
        print(f"{x:.6f}\t{y:.6f}")

    print("\n# Interpolated values")
    print("x\tP(x)\tdP(x)/dx")
    M = 25
    for i in range(M + 1):
        x = X[0] + (X[-1] - X[0]) * i / M
        p, dp = spline(x)
        print(f"{x:.6f}\t{p:.6f}\t{dp:.6f}")

References