PPOLY
Overview
The PPOLY function constructs a piecewise polynomial in the power basis, defined by a set of polynomial coefficients and breakpoints. This is useful for representing and evaluating polynomials that are defined piecewise over different intervals, such as in interpolation, curve fitting, and numerical analysis. The polynomial between each pair of breakpoints and is given by:
where is the degree of the polynomial, are the coefficients for the -th interval, and are the breakpoints. For more details, see the SciPy PPoly documentation .
This example function is provided as-is without any representation of accuracy.
Usage
To use the function in Excel:
=PPOLY(c, x, [eval_points], [extrapolate])c(2D list, required): Coefficient matrix of shape (degree+1, n_intervals). Each column contains the coefficients for one interval, ordered from highest to lowest degree. Must be a 2D list. 1D lists are not supported.x(2D list, required): Column vector of breakpoints of lengthn_intervals+1. Must be a 2D list with exactly one column and at least two rows.eval_points(2D list, optional, default=x): Points at which to evaluate the piecewise polynomial. Scalars are treated as single-cell tables. Must be a 2D list when provided.extrapolate(bool, optional, default=True): Whether to extrapolate outside the breakpoints. String values such as"TRUE"/"FALSE"are coerced to booleans.
The function returns a 2D list of evaluated values at the specified points, or an error message (string) if the input is invalid. When extrapolate is FALSE, out-of-range evaluations return None in the corresponding cells.
Examples
Example 1: Quadratic Piecewise Polynomial
This example constructs a quadratic piecewise polynomial with two intervals and evaluates it at the breakpoints.
Inputs:
| c | x | eval_points | extrapolate | |
|---|---|---|---|---|
| 1 | 0 | 0 | 0 | True |
| 0 | 1 | 1 | 0.5 | |
| -1 | 1 | 2 | 1 |
Excel formula:
=PPOLY({1,0;0,1;-1,1}, {0;1;2}, {0;0.5;1}, TRUE)Expected output:
| Result |
|---|
| -1.000 |
| -0.750 |
| 1.000 |
This means the polynomial evaluates to -1 at the left endpoint, -0.75 at 0.5, and 1 at the right endpoint.
Example 2: Linear Piecewise Polynomial
This example constructs a linear piecewise polynomial and evaluates it at several points.
Inputs:
| c | x | eval_points | extrapolate | |
|---|---|---|---|---|
| 2 | 1 | 0 | 0 | False |
| 0 | 1 | 1 | 0.5 | |
| 2 | 1 |
Excel formula:
=PPOLY({2,1;0,1}, {0;1;2}, {0;0.5;1}, FALSE)Expected output:
| Result |
|---|
| 0.000 |
| 1.000 |
| 1.000 |
Example 3: Default Evaluation at Breakpoints
This example omits the eval_points argument, so the polynomial is evaluated at the breakpoints.
Inputs:
| c | x | extrapolate | |
|---|---|---|---|
| 1 | 2 | 0 | True |
| 0 | 1 | 1 | |
| 2 |
Excel formula:
=PPOLY({1,2;0,1}, {0;1;2}, , TRUE)Expected output:
| Result |
|---|
| 0.000 |
| 1.000 |
| 3.000 |
This example illustrates the default evaluation when no additional eval_points are supplied and uses a blank placeholder for the optional argument in Excel.
Example 4: Quadratic Evaluation Without Extrapolation
This example evaluates the quadratic piecewise polynomial at two points while disabling extrapolation.
Inputs:
| c | x | eval_points | extrapolate | |
|---|---|---|---|---|
| 1 | 0 | 0 | 0 | False |
| 0 | 1 | 1 | 1 | |
| -1 | 1 | 2 |
Excel formula:
=PPOLY({1,0;0,1;-1,1}, {0;1;2}, {0;1}, FALSE)Expected output:
| Result |
|---|
| -1.000 |
| 1.000 |
Python Code
from typing import List, Optional, Union
import numpy as np
from scipy.interpolate import PPoly as scipy_ppoly
ScalarInput = Union[int, float, bool, str, None]
MatrixInput = Union[ScalarInput, List[List[ScalarInput]]]
MatrixOutput = List[List[Union[float, bool, str, None]]]
ReturnType = Union[str, MatrixOutput]
def ppoly(
c: MatrixInput,
x: MatrixInput,
eval_points: Optional[MatrixInput] = None,
extrapolate: Union[bool, int, float, str] = True,
) -> ReturnType:
"""
Construct and evaluate a piecewise polynomial in the power basis.
Args:
c: 2D list of coefficients (degree+1, n_intervals). Must be a 2D list.
x: 2D list of breakpoints (n_intervals+1, 1). Must be a 2D list (column vector) with at least two rows.
eval_points: 2D list of points to evaluate at (default: x). Must be a 2D list.
extrapolate: Whether to extrapolate outside breakpoints (default: True).
Returns:
2D list of evaluated values, or error message (str) if input is invalid.
This example function is provided as-is without any representation of accuracy.
"""
def _wrap_scalar(value: ScalarInput) -> List[List[ScalarInput]]:
return [[value]]
def _normalize_matrix(arg: MatrixInput, name: str) -> Union[str, List[List[ScalarInput]]]:
# Convert scalars to single-cell 2D lists and ensure structural validity.
candidate: List[List[ScalarInput]]
if isinstance(arg, list):
if not arg:
return f"Invalid input: {name} must be a 2D list with at least one row."
if any(not isinstance(row, list) or not row for row in arg):
return f"Invalid input: {name} must be a 2D list with no empty rows."
if any(
any(isinstance(cell, list) for cell in row)
for row in arg
):
return f"Invalid input: {name} must contain scalar values."
candidate = arg
else:
candidate = _wrap_scalar(arg)
return candidate
def _coerce_floats(matrix: List[List[ScalarInput]], name: str) -> Union[str, List[List[float]]]:
# Convert all values to floats while validating numeric content.
converted: List[List[float]] = []
for row in matrix:
converted_row: List[float] = []
for value in row:
try:
converted_row.append(float(value))
except Exception:
return f"Invalid input: {name} must contain numeric values."
converted.append(converted_row)
array_view = np.asarray(converted, dtype=float)
if not np.all(np.isfinite(array_view)):
return f"Invalid input: {name} must contain finite numeric values."
return converted
def _coerce_bool(value: Union[bool, int, float, str], name: str) -> Union[str, bool]:
# Interpret Excel-style boolean representations.
if isinstance(value, bool):
return value
if isinstance(value, (int, float)) and value in (0, 1):
return bool(value)
if isinstance(value, str):
lowered = value.strip().lower()
if lowered in {"true", "1", "yes"}:
return True
if lowered in {"false", "0", "no"}:
return False
return f"Invalid input: {name} must be a boolean value."
normalized_c = _normalize_matrix(c, "c")
if isinstance(normalized_c, str):
return normalized_c
if len(normalized_c) < 1:
return "Invalid input: c must contain at least one row."
row_lengths = {len(row) for row in normalized_c}
if len(row_lengths) != 1:
return "Invalid input: all rows in c must have the same length."
n_intervals = next(iter(row_lengths))
if n_intervals < 1:
return "Invalid input: c must define at least one interval."
normalized_x = _normalize_matrix(x, "x")
if isinstance(normalized_x, str):
return normalized_x
if len(normalized_x) != n_intervals + 1:
return "Invalid input: x must be a column vector of length n_intervals + 1."
if any(len(row) != 1 for row in normalized_x):
return "Invalid input: x must be a 2D column vector."
coerce_c = _coerce_floats(normalized_c, "c")
if isinstance(coerce_c, str):
return coerce_c
coerce_x = _coerce_floats(normalized_x, "x")
if isinstance(coerce_x, str):
return coerce_x
x_values = [row[0] for row in coerce_x]
diffs = np.diff(x_values)
if not (np.all(diffs > 0) or np.all(diffs < 0)):
return "Invalid input: x must be strictly monotonic."
if eval_points is None:
eval_points_matrix = [[row[0]] for row in coerce_x]
else:
normalized_eval = _normalize_matrix(eval_points, "eval_points")
if isinstance(normalized_eval, str):
return normalized_eval
eval_points_matrix = normalized_eval
if len(eval_points_matrix) < 1 or any(len(row) < 1 for row in eval_points_matrix):
return "Invalid input: eval_points must be a 2D list with no empty rows."
coerced_eval = _coerce_floats(eval_points_matrix, "eval_points")
if isinstance(coerced_eval, str):
return coerced_eval
extrapolate_value = _coerce_bool(extrapolate, "extrapolate")
if isinstance(extrapolate_value, str):
return extrapolate_value
# Prepare arrays for SciPy evaluation.
coefficients = np.asarray(coerce_c, dtype=float)
breakpoints = np.asarray(x_values, dtype=float)
eval_flat: List[float] = []
eval_shape: List[int] = []
for row in coerced_eval:
eval_shape.append(len(row))
eval_flat.extend(row)
eval_array = np.asarray(eval_flat, dtype=float)
try:
piecewise = scipy_ppoly(coefficients, breakpoints, extrapolate=extrapolate_value)
evaluations = piecewise(eval_array)
except Exception as exc:
return f"scipy.PPoly error: {exc}"
evaluations = np.asarray(evaluations, dtype=float)
output: MatrixOutput = []
start_index = 0
for length in eval_shape:
row_values: List[Union[float, bool, str, None]] = []
for offset in range(length):
value = evaluations[start_index + offset]
if not np.isfinite(value):
row_values.append(None)
else:
row_values.append(float(value))
start_index += length
output.append(row_values)
return output