Skip to content

Sliding Technique

The Sliding Technique enables Chebyshev approximation of high-dimensional functions by decomposing them into a sum of low-dimensional interpolants. This sidesteps the curse of dimensionality at the cost of losing cross-group interactions.

Motivation

A full tensor Chebyshev interpolant on \(n\) dimensions with \(m\) nodes per dimension requires \(m^n\) function evaluations. For \(n = 10\) and \(m = 11\), that is over 25 billion evaluations — clearly infeasible.

The sliding technique partitions the dimensions into small groups and builds a separate Chebyshev interpolant (a slide) for each group, with all other dimensions fixed at a pivot point. The total cost becomes the sum of the group grid sizes rather than their product.

Algorithm

Given \(f: \mathbb{R}^n \to \mathbb{R}\), a pivot point \(\mathbf{z} = (z_1, \ldots, z_n)\), and a partition of dimensions into \(k\) groups (Ruiz & Zeron 2021, Ch. 7):

  1. Evaluate the pivot value \(v = f(\mathbf{z})\).
  2. For each group \(i\), build a slide \(s_i\) — a Chebyshev interpolant on the group's dimensions, with all other dimensions fixed at their pivot values.
  3. Evaluate using the additive formula:
\[ f(\mathbf{x}) \approx v + \sum_{i=1}^{k} \bigl[ s_i(\mathbf{x}_{G_i}) - v \bigr] \]

where \(\mathbf{x}_{G_i}\) denotes the components of \(\mathbf{x}\) belonging to group \(i\).

When to Use Sliding

Sliding works well when:

  • The function is additively separable or nearly so (e.g., \(\sin(x_1) + \sin(x_2) + \sin(x_3)\)).
  • Cross-group interactions are weak relative to within-group effects.
  • The number of dimensions is too large for full tensor interpolation (say, \(n > 6\)).

Sliding does not work well when:

  • Variables in different groups are strongly coupled (e.g., Black-Scholes where \(S\), \(T\), and \(\sigma\) interact multiplicatively).
  • High accuracy is required far from the pivot point.

Alternative: Tensor Train

For general (non-separable) high-dimensional functions, consider ChebyshevTT instead. TT-Cross captures cross-variable coupling that the sliding decomposition misses, at the cost of using finite differences for derivatives instead of analytical spectral differentiation.

Choosing the partition

Group variables that have strong non-linear interactions together. For example, if \(f = x_1^3 x_2^2 + x_3\), group \((x_1, x_2)\) in one slide and \(x_3\) in another.

Usage

import math
from pychebyshev import ChebyshevSlider

# Additively separable function
def f(x, _):
    return math.sin(x[0]) + math.sin(x[1]) + math.sin(x[2])

slider = ChebyshevSlider(
    function=f,
    num_dimensions=3,
    domain=[[-1, 1], [-1, 1], [-1, 1]],
    n_nodes=[11, 11, 11],
    partition=[[0], [1], [2]],       # each dim is its own slide
    pivot_point=[0.0, 0.0, 0.0],
)
slider.build()

# Evaluate function value
val = slider.eval([0.5, 0.3, -0.2], [0, 0, 0])

# Evaluate derivative w.r.t. x0
dfdx0 = slider.eval([0.5, 0.3, -0.2], [1, 0, 0])

Multi-dimensional slides

For functions with within-group coupling, use larger groups:

def g(x, _):
    return x[0]**3 * x[1]**2 + math.sin(x[2]) + math.sin(x[3])

slider = ChebyshevSlider(
    function=g,
    num_dimensions=4,
    domain=[[-2, 2], [-2, 2], [-1, 1], [-1, 1]],
    n_nodes=[12, 12, 8, 8],
    partition=[[0, 1], [2], [3]],    # 2D + 1D + 1D
    pivot_point=[0.0, 0.0, 0.0, 0.0],
)
slider.build()

Build cost comparison

# Full tensor: 12 * 12 * 8 * 8 = 9,216 evaluations
# Sliding:     12*12 + 8 + 8   = 160 evaluations  (57x fewer)
print(f"Slider build evaluations: {slider.total_build_evals}")

Derivatives

The slider supports analytical derivatives through its slides. Only the slide containing the differentiated dimension contributes:

\[ \frac{\partial}{\partial x_j} f(\mathbf{x}) \approx \frac{\partial}{\partial x_j} s_i(\mathbf{x}_{G_i}) \]

where \(j \in G_i\). The pivot value \(v\) is constant and drops out.

# Multiple derivatives at once
results = slider.eval_multi(
    [0.5, 0.3, -0.2],
    [
        [0, 0, 0],  # function value
        [1, 0, 0],  # d/dx0
        [0, 1, 0],  # d/dx1
        [0, 0, 1],  # d/dx2
    ],
)

Limitations

Cross-group derivatives are zero

Because slides are independent functions of disjoint variable groups, mixed partial derivatives across groups are exactly zero. For example, with partition [[0, 1], [2]]:

  • \(\frac{\partial^2 f}{\partial x_0 \partial x_1}\) — computed within the [0, 1] slide (correct)
  • \(\frac{\partial^2 f}{\partial x_0 \partial x_2}\) — returns 0 (x₀ and x₂ are in different slides)

This is mathematically correct for the sliding approximation, but may differ from the true function's cross-derivatives. If cross-group sensitivities matter, group those variables together or use full tensor interpolation.

Accuracy degrades far from pivot

The sliding approximation is most accurate near the pivot point. As the evaluation point moves away from the pivot in multiple dimensions simultaneously, cross-coupling errors accumulate. For strongly coupled functions like Black-Scholes, errors of 20--50% at domain boundaries have been observed in numerical experiments (see compare_methods_time_accuracy.py in the repository).

References

  • Ruiz, G. & Zeron, M. (2021). Machine Learning for Risk Calculations. Wiley Finance. Chapter 7.

API Reference

Chebyshev Sliding approximation for high-dimensional functions.

Decomposes f(x_1, ..., x_n) into a sum of low-dimensional Chebyshev interpolants (slides) around a pivot point z:

f(x) ≈ f(z) + Σ_i [s_i(x_group_i) - f(z)]

where each slide s_i is a ChebyshevApproximation built on a subset of dimensions with the remaining dimensions fixed at z.

This trades accuracy for dramatically reduced build cost: instead of evaluating f at n_1 × n_2 × ... × n_d grid points (exponential), the slider evaluates at n_1 × n_2 + n_3 × n_4 + ... (sum of products within each group).

Parameters:

Name Type Description Default
function callable

Function to approximate. Signature: f(point, data) -> float where point is a list of floats and data is arbitrary additional data (can be None).

required
num_dimensions int

Total number of input dimensions.

required
domain list of (float, float)

Bounds [lo, hi] for each dimension.

required
n_nodes list of int

Number of Chebyshev nodes per dimension.

required
partition list of list of int

Grouping of dimension indices into slides. Each dimension must appear in exactly one group. E.g. [[0,1,2], [3,4]] creates a 3D slide for dims 0,1,2 and a 2D slide for dims 3,4.

required
pivot_point list of float

Reference point z around which slides are built.

required
max_derivative_order int

Maximum derivative order to pre-compute (default 2).

2

Examples:

>>> import math
>>> def f(x, _):
...     return math.sin(x[0]) + math.sin(x[1]) + math.sin(x[2])
>>> slider = ChebyshevSlider(
...     f, 3, [[-1,1], [-1,1], [-1,1]], [11,11,11],
...     partition=[[0], [1], [2]],
...     pivot_point=[0.0, 0.0, 0.0],
... )
>>> slider.build(verbose=False)
>>> round(slider.eval([0.5, 0.3, 0.1], [0,0,0]), 4)
0.8764

total_build_evals property

Total number of function evaluations used during build.

build(verbose=True)

Build all slides by evaluating the function at slide-specific grids.

For each slide, dimensions outside the slide group are fixed at their pivot values.

Parameters:

Name Type Description Default
verbose bool or int

If True or 1, print build progress. If 2, also show a tqdm progress bar (requires pychebyshev[viz]). Default is True.

True

get_derivative_id(derivative_order)

Register a derivative-orders tuple and return a stable session-local int.

eval(point, derivative_order=None, *, derivative_id=None)

Evaluate the slider approximation at a point.

Uses Equation 7.5 from Ruiz & Zeron (2021): f(x) ≈ f(z) + Σ_i [s_i(x_i) - f(z)]

For derivatives, only the slide containing that dimension contributes.

Parameters:

Name Type Description Default
point list of float

Evaluation point in the full n-dimensional space.

required
derivative_order list of int

Derivative order for each dimension (0 = function value). Mutually exclusive with derivative_id.

None
derivative_id int

Session-local integer returned by get_derivative_id(). Mutually exclusive with derivative_order.

None

Returns:

Type Description
float

Approximated function value or derivative.

eval_multi(point, derivative_orders)

Evaluate slider at multiple derivative orders for the same point.

Parameters:

Name Type Description Default
point list of float

Evaluation point in the full n-dimensional space.

required
derivative_orders list of list of int

Each inner list specifies derivative order per dimension.

required

Returns:

Type Description
list of float

Results for each derivative order.

error_estimate()

Estimate the sliding approximation error.

Returns the sum of per-slide Chebyshev error estimates. Each slide's error is estimated independently using the Chebyshev coefficient method from Ruiz & Zeron (2021), Section 3.4.

Note: This captures per-slide interpolation error only. Cross-group interaction error (inherent to the sliding decomposition) is not included.

Returns:

Type Description
float

Estimated interpolation error (per-slide sum).

Raises:

Type Description
RuntimeError

If build() has not been called.

__getstate__()

Return picklable state, excluding the original function.

__setstate__(state)

Restore state from a pickled dict.

is_construction_finished()

Return True iff this slider is built and usable.

get_constructor_type()

Return the class name.

get_used_ns()

Return the per-dim node count list.

set_descriptor(descriptor)

Set a free-form text label on this slider.

Parameters:

Name Type Description Default
descriptor str

Label to attach to this slider.

required

Raises:

Type Description
TypeError

If descriptor is not a string.

get_descriptor()

Return the descriptor label (default "").

get_max_derivative_order()

Return the maximum derivative order this interpolant was constructed with. Derivative orders up to and including this value are queryable via eval(point, derivative_order=...).

get_num_evaluation_points()

Return the number of points in the evaluation grid (slides only).

The pivot point's f-evaluation during build is not counted — the pivot is a build-time singleton, not a grid point. Equals sum(prod(n_nodes[d] for d in group) for group in partition).

Returns:

Type Description
int

Total number of slide grid points (excluding the pivot evaluation).

get_evaluation_points()

Return the slide evaluation grid lifted into d-D coordinate space.

Each slide's local grid is returned with off-group dims set to the pivot value. The pivot row itself is not included (the pivot is a build-time singleton, not a grid point). Returns shape (get_num_evaluation_points(), num_dimensions).

Returns:

Type Description
ndarray

Shape (N, num_dimensions) where N equals :meth:get_num_evaluation_points.

clone()

Return an independent deep copy of this interpolant.

All mutable state (slides, descriptor, additional_data) is duplicated. Mutating the clone does not affect the original.

Note

Like :meth:save / :meth:load, the source function callable is not duplicated -- the clone has function = None. All evaluation, algebra, serialization, and v0.16 surface methods continue to work; only :meth:build (which requires a function) does not.

Returns:

Type Description
ChebyshevSlider

A new instance with deep-copied state.

is_dimensionality_allowed(num_dimensions) staticmethod

Return whether this interpolant class supports the given number of dimensions. Returns True for any num_dimensions >= 1. Provided as a hook for future per-class capability caps.

save(path)

Save the built slider to a file.

The original function is not saved — only the numerical data needed for evaluation. The saved file can be loaded with :meth:load without access to the original function.

Parameters:

Name Type Description Default
path str or path - like

Destination file path.

required

Raises:

Type Description
RuntimeError

If the slider has not been built yet.

load(path) classmethod

Load a previously saved slider from a file.

The loaded object can evaluate immediately; no rebuild is needed. The function attribute will be None. Assign a new function before calling build() again if a rebuild is desired.

Parameters:

Name Type Description Default
path str or path - like

Path to the saved file.

required

Returns:

Type Description
ChebyshevSlider

The restored slider.

Warns:

Type Description
UserWarning

If the file was saved with a different PyChebyshev version.

.. warning::

This method uses :mod:pickle internally. Pickle can execute arbitrary code during deserialization. Only load files you trust.

extrude(params)

Add new dimensions where the function is constant.

Each new dimension becomes its own single-dim slide group with tensor_values = np.full(n, pivot_value), so that s_new(x) - pivot_value = 0 for all x (no contribution to the sliding sum). This is the partition-of-unity property: the barycentric weights sum to 1, so a constant tensor produces the same value for any coordinate.

Existing slide groups have their dimension indices remapped to account for the inserted dimensions.

Parameters:

Name Type Description Default
params tuple or list of tuples

Single (dim_index, (lo, hi), n_nodes) or a list of such tuples. dim_index is the position in the output space (0-indexed). n_nodes must be >= 2 and lo < hi.

required

Returns:

Type Description
ChebyshevSlider

A new, higher-dimensional slider (already built). The result has function=None.

Raises:

Type Description
RuntimeError

If the slider has not been built yet.

TypeError

If dim_index is not an integer.

ValueError

If dim_index is out of range, duplicated, lo >= hi, or n_nodes < 2.

slice(params)

Fix one or more dimensions at given values, reducing dimensionality.

Two cases per sliced dimension:

  • Multi-dim group: The slide's ChebyshevApproximation is sliced at the local dimension index via barycentric contraction. When the slice value coincides with a Chebyshev node (within 1e-14), the contraction reduces to an exact np.take (fast path). The dimension is removed from the group.
  • Single-dim group: The slide is evaluated at the value, giving a constant s_val. The shift delta = s_val - pivot_value is absorbed into pivot_value and each remaining slide's tensor_values, and the group is removed entirely.

Remaining dimension indices in all groups are remapped downward to stay contiguous.

Parameters:

Name Type Description Default
params tuple or list of tuples

Single (dim_index, value) or a list of such tuples. value must lie within the domain for that dimension.

required

Returns:

Type Description
ChebyshevSlider

A new, lower-dimensional slider (already built). The result has function=None.

Raises:

Type Description
RuntimeError

If the slider has not been built yet.

TypeError

If dim_index is not an integer.

ValueError

If a slice value is outside the domain, if slicing all dimensions, or if dim_index is out of range or duplicated.

integrate(dims=None, bounds=None)

Integrate the slider approximation over one or more dimensions.

Uses the closed-form decomposition of the sliding sum f(x) ≈ pv + Σ_i [s_i(x_{G_i}) - pv]. Each slide's integral is computed via :meth:ChebyshevApproximation.integrate.

Full integration (dims=None) collapses every dimension and returns a scalar. Partial integration returns a new :class:ChebyshevSlider over the surviving dimensions, with an adjusted pivot value that absorbs the contributions of every slide whose group is fully integrated.

Parameters:

Name Type Description Default
dims int, list of int, or None

Dimensions to integrate out. None integrates every dimension and returns a scalar.

None
bounds tuple, list of tuple/None, or None

Sub-interval bounds for integration. None (default) integrates over the full domain of each integrated dimension. A list of tuples/None provides per-dim bounds with positional correspondence to dims (after sorting).

None

Returns:

Type Description
float or ChebyshevSlider

Scalar if every dimension is integrated; otherwise a lower-dimensional slider (already built, function=None).

Raises:

Type Description
RuntimeError

If build() has not been called.

ValueError

If any dimension index is out of range/duplicated, or if bounds are outside the domain.

roots(dim=None, fixed=None)

Find all roots of the slider along a specified dimension.

Reduces to a 1-D problem by slicing all other dimensions to their fixed values, then delegates to ChebyshevApproximation.roots() (which uses the colleague matrix eigenvalue method, Good 1961).

Parameters:

Name Type Description Default
dim int or None

Dimension along which to find roots. For 1-D sliders, defaults to 0.

None
fixed dict or None

For multi-D sliders, {dim_index: value} for all dimensions except dim.

None

Returns:

Type Description
ndarray

Sorted real root locations in the physical domain.

Raises:

Type Description
RuntimeError

If build() has not been called.

ValueError

If dim / fixed validation fails or values are out of domain.

References

Good (1961), "The colleague matrix", Quarterly J. Math. 12(1):61–68. Trefethen (2013), "Approximation Theory and Approximation Practice", SIAM, Chapter 18.

minimize(dim=None, fixed=None)

Find the minimum value of the slider along a dimension.

Computes derivative roots to locate critical points, then evaluates at all critical points and domain endpoints. For multi-D sliders, all dimensions except the target must be fixed.

Parameters:

Name Type Description Default
dim int or None

Dimension along which to minimize. Defaults to 0 for 1-D.

None
fixed dict or None

For multi-D, {dim_index: value} for all other dims.

None

Returns:

Type Description
(value, location) : (float, float)

The minimum value and its coordinate in the target dimension.

Raises:

Type Description
RuntimeError

If build() has not been called.

ValueError

If dim / fixed validation fails.

maximize(dim=None, fixed=None)

Find the maximum value of the slider along a dimension.

See minimize() for parameter details. Returns (max_value, max_location) instead.

plot_1d(ax=None, n_points=200, fixed=None)

Plot the 1-D slice of this interpolant.

Requires the optional pychebyshev[viz] dependency group.

Parameters:

Name Type Description Default
ax Axes | None

Pre-existing axes (creates a new figure if None).

None
n_points int

Number of sample points along the free dim.

200
fixed dict[int, float] | None

Map of dim → value to constrain other dims, leaving exactly one free.

None

Returns:

Type Description
Axes

plot_2d_surface(ax=None, n_points=50, fixed=None)

Plot a 3-D surface for the 2-D slice. Requires matplotlib.

plot_2d_contour(ax=None, n_points=50, n_levels=20, fixed=None)

Plot a filled-contour 2-D slice. Requires matplotlib.