BPOLY
Overview
The BPOLY function constructs a piecewise polynomial in the Bernstein basis, allowing for smooth interpolation between specified breakpoints using Bernstein polynomials. This is useful in numerical analysis, computer graphics, and engineering for representing curves and surfaces with desirable properties such as smoothness and stability. The polynomial between each pair of breakpoints and is expressed as:
where is the degree of the polynomial, are the Bernstein coefficients for interval , and is the Bernstein basis polynomial:
This wrapper exposes the core arguments c, x, x_eval, and extrapolate from scipy.interpolate.BPoly while fixing the interpolation axis to 0 and requiring the breakpoints to be supplied as a single-column table. For more details, see the SciPy BPoly documentation and Wikipedia: Bernstein polynomial .
This example function is provided as-is without any representation of accuracy.
Usage
To use the function in Excel:
=BPOLY(c, x, x_eval, [extrapolate])c(2D list, required): Bernstein coefficients of shape(degree + 1, number of intervals). Each row must have the same number of columns and contain only finite numeric values.x(2D list, required): Breakpoints as a single-column table of shape(number of intervals + 1, 1). Values must be finite and strictly monotonic (all increasing or all decreasing).x_eval(2D list, required): Points at which to evaluate the polynomial, given as a single-column table with finite numeric values.extrapolate(bool or string (enum), optional, default=True): Whether to extrapolate outside the breakpoint range. Valid options:TRUE,FALSE, or the stringperiodicfor periodic extrapolation.
The function returns a 2D list of evaluated values (float) with one column, or an error message (string) if the input is invalid.
Examples
Example 1: Quadratic Bernstein Polynomial
This example constructs a quadratic polynomial on the interval [0, 1, 2, 3] with coefficients 1, 2, 3, and evaluates it at the breakpoints.
Inputs:
| c | x | x_eval | extrapolate | ||
|---|---|---|---|---|---|
| 1 | 2 | 3 | 0 | 0 | True |
| 1 | 1 | ||||
| 2 | 2 | ||||
| 3 | 3 |
Excel formula:
=BPOLY({1,2,3}, {0;1;2;3}, {0;1;2;3})Expected output:
| Result |
|---|
| 1.000 |
| 2.000 |
| 3.000 |
| 3.000 |
Example 2: Cubic Bernstein Polynomial with Two Intervals
Inputs:
| c | x | x_eval | extrapolate | |||
|---|---|---|---|---|---|---|
| 0 | 1 | 2 | 3 | 0 | 0 | True |
| 1 | 2 | 3 | 4 | 0.25 | 0.25 | |
| 0.5 | 0.5 | |||||
| 0.75 | 0.75 | |||||
| 1 | 1 |
Excel formula:
=BPOLY({0,1,2,3;1,2,3,4}, {0;0.25;0.5;0.75;1}, {0;0.25;0.5;0.75;1})Expected output:
| Result |
|---|
| 0.000 |
| 1.000 |
| 2.000 |
| 3.000 |
| 4.000 |
Example 3: No Extrapolation
Inputs:
| c | x | x_eval | extrapolate | ||
|---|---|---|---|---|---|
| 1 | 2 | 3 | 0 | 0 | False |
| 1 | 1 | ||||
| 2 | 2 | ||||
| 3 | 3 |
Excel formula:
=BPOLY({1,2,3}, {0;1;2;3}, {0;1;2;3}, FALSE)Expected output:
| Result |
|---|
| 1.000 |
| 2.000 |
| 3.000 |
| 3.000 |
Example 4: Periodic Extrapolation
Inputs:
| c | x | x_eval | extrapolate | ||
|---|---|---|---|---|---|
| 1 | 2 | 3 | 0 | 0 | ”periodic” |
| 1 | 1 | ||||
| 2 | 2 | ||||
| 3 | 3 |
Excel formula:
=BPOLY({1,2,3}, {0;1;2;3}, {0;1;2;3}, "periodic")Expected output:
| Result |
|---|
| 1.000 |
| 2.000 |
| 3.000 |
| 1.000 |
Python Code
from typing import List, Union
import numpy as np
from scipy.interpolate import BPoly as scipy_bpoly
Scalar = Union[float, int, bool, str, None]
MatrixInput = Union[Scalar, List[List[Scalar]]]
def bpoly(
c: MatrixInput,
x: MatrixInput,
x_eval: MatrixInput,
extrapolate: bool | str = True,
) -> list[list[float]] | str:
"""
Construct a piecewise polynomial in the Bernstein basis and evaluate it at specified points.
Args:
c: 2D list of Bernstein coefficients, shape (degree + 1, number of intervals). Each row must have the same number of columns. Scalars are treated as a single-row 2D list.
x: 2D list of breakpoints, shape (number of intervals + 1, 1). Values must be strictly monotonic (all increasing or all decreasing). Scalars are treated as a 2D column with one row per breakpoint.
x_eval: 2D list of points at which to evaluate the polynomial, shape (n, 1). Scalars are treated as a 2D column with one row per evaluation point.
extrapolate: Bool or str, optional. Whether to extrapolate outside the breakpoints. If 'periodic', uses periodic extrapolation. Default is True.
Returns:
2D list of evaluated values (float), or an error message (str) if input is invalid.
This example function is provided as-is without any representation of accuracy.
"""
# Normalize scalar inputs into 2D list form
def _wrap_scalar_to_matrix(value: MatrixInput) -> MatrixInput:
if isinstance(value, list):
return value
return [[value]]
c_wrapped = _wrap_scalar_to_matrix(c)
x_wrapped = _wrap_scalar_to_matrix(x)
x_eval_wrapped = _wrap_scalar_to_matrix(x_eval)
# Validate c
if not isinstance(c_wrapped, list) or not c_wrapped:
return "Invalid input: c must be a 2D list with at least one row."
if not all(isinstance(row, list) and row for row in c_wrapped):
return "Invalid input: c must be a 2D list without empty rows."
if len({len(row) for row in c_wrapped}) != 1:
return "Invalid input: c must have rows of equal length."
# Validate x
if not isinstance(x_wrapped, list) or not x_wrapped:
return "Invalid input: x must be a 2D column vector (list of lists)."
if not all(isinstance(row, list) and len(row) == 1 for row in x_wrapped):
return "Invalid input: x must be a 2D column vector (list of lists, each with one value)."
# Validate x_eval
if not isinstance(x_eval_wrapped, list) or not x_eval_wrapped:
return "Invalid input: x_eval must be a 2D column vector (list of lists)."
if not all(isinstance(row, list) and len(row) == 1 for row in x_eval_wrapped):
return "Invalid input: x_eval must be a 2D column vector (list of lists, each with one value)."
# Validate extrapolate argument
if not (isinstance(extrapolate, bool) or (isinstance(extrapolate, str) and extrapolate == "periodic")):
return "Invalid input: extrapolate must be a bool or 'periodic'."
# Ensure consistent interval configuration
try:
c_array = np.array([[float(item) for item in row] for row in c_wrapped], dtype=float)
except (TypeError, ValueError):
return "Invalid input: c must contain numeric values."
if c_array.ndim != 2:
return "Invalid input: c must be a 2D list."
if not np.all(np.isfinite(c_array)):
return "Invalid input: c must contain finite numeric values."
# The second dimension corresponds to intervals
n_intervals = c_array.shape[1]
if n_intervals < 1:
return "Invalid input: c must define at least one interval."
try:
x_array = np.array([[float(row[0]) for row in x_wrapped]], dtype=float).flatten()
except (TypeError, ValueError):
return "Invalid input: x must contain numeric values."
if not np.all(np.isfinite(x_array)):
return "Invalid input: x must contain finite numeric values."
if x_array.size != n_intervals + 1:
return "Invalid input: x must have (number of intervals + 1) rows."
# Ensure the breakpoint sequence is strictly monotonic
diffs = np.diff(x_array)
if diffs.size == 0 or not (np.all(diffs > 0) or np.all(diffs < 0)):
return "Invalid input: x must be strictly monotonic (all increasing or all decreasing)."
try:
x_eval_array = np.array([[float(row[0]) for row in x_eval_wrapped]], dtype=float).flatten()
except (TypeError, ValueError):
return "Invalid input: x_eval must contain numeric values."
if not np.all(np.isfinite(x_eval_array)):
return "Invalid input: x_eval must contain finite numeric values."
# Evaluate the polynomial and guard against non-finite values
try:
poly = scipy_bpoly(c_array, x_array, extrapolate=extrapolate)
y_values = poly(x_eval_array)
except Exception as exc:
return f"scipy.interpolate.BPoly error: {exc}"
if not np.all(np.isfinite(y_values)):
return "Invalid result: scipy.interpolate.BPoly returned non-finite values."
# Convert the result to a 2D list for Excel compatibility
return [[float(val)] for val in y_values.tolist()]