Skip to main content

BiLinearInterpolation

Trait BiLinearInterpolation 

Source
pub trait BiLinearInterpolation<Point, Input> {
    // Required method
    fn bilinear_interpolate(
        &self,
        x: Input,
    ) -> Result<Point, InterpolationError>;
}
Expand description

A trait for bilinear interpolation on 2D data grids.

§Purpose

The BiLinearInterpolation trait is designed to perform bilinear interpolation to estimate intermediate values within a grid of 2D points. This method is often used in numerical computation tasks, such as image processing, terrain modeling, and scientific data visualization.

§Type Parameters

  • Point: The output type, typically used to represent the interpolated 2D point.
  • Input: The input type for the interpolation parameter, typically a scalar value.

§Associated Type

  • Error: Defines the type returned in case of a failure during interpolation.

§Method

  • bilinear_interpolate: Computes the interpolated value at the given input, returning either the result or an error if the operation cannot proceed.

§Errors

Any errors encountered during interpolation are encapsulated in the type Error. This trait is expected to return meaningful errors in cases like:

  • Insufficient or invalid data for computation.
  • Inputs that are out of bounds for the given dataset.
  • Issues specific to the interpolation logic.

§Example Usage

Below is an example demonstrating how an implementing struct might use this trait:

use rust_decimal::Decimal;
use optionstratlib_math::curves::Point2D;
use optionstratlib_math::error::InterpolationError;
use optionstratlib_math::geometrics::BiLinearInterpolation;
struct GridInterpolator {
    // Implementation-specific fields like the grid or data.
}

impl BiLinearInterpolation<Point2D, Decimal> for GridInterpolator {

    fn bilinear_interpolate(&self, x: Decimal) -> Result<Point2D, InterpolationError> {
        Ok(Point2D::new(x, x)) // Placeholder implementation
    }
}

In this example:

  • GridInterpolator implements the trait for bilinear interpolation.
  • The bilinear_interpolate method calculates the interpolated Point2D for a given x value.
  • Point2D: A struct representing a 2D point with x and y coordinates.
  • CurvesError: A recommended error type for detailed error categorization.

§See Also

§One point per abscissa

Every interpolator here reads its sample as a function of the abscissa: at most one ordinate per x. See Curve::new for the rule and for why no constructor enforces it. A sample carrying several ordinates at one abscissa is outside the contract: the grid cell around a repeated abscissa has zero width and the slope across it is undefined, so the answer is InterpolationError::DegenerateInterval.

That holds on every branch, including the exact match: the Curve implementation short-circuits an x that lands on a sample, and on a stack of ordinates at that x it reports the degeneracy rather than returning the lowest of them.

A projection of a surface is multi-valued by construction, which is why Surface::project_onto returns a Vec<Point2D> and not a curve; aggregate it before interpolating.

§Cells at the edge of the sample

A cell wants samples on both sides of the query along every axis, which the samples at the upper edge of the domain cannot supply. The Curve implementation builds its cell out of two consecutive segments and clamps the far one to the curve’s last segment where it would run off the end, so the segment holding the query is always one edge of the cell and the answer stays a convex combination of the four ordinates read; that implementation documents the rule and its consequences in full. The Surface one picks the four samples nearest the query, so its cell has no end to run off.

Required Methods§

Source

fn bilinear_interpolate(&self, x: Input) -> Result<Point, InterpolationError>

Performs bilinear interpolation to compute a value for the given input.

§Parameters
  • x: The input value (e.g., an x coordinate in 2D space) for which the interpolation is performed.
§Returns
  • Ok(Point): The interpolated point (e.g., a Point2D) representing the computed values.
  • Err(Self::Error): An error indicating why the interpolation could not be performed.
§Example
use std::collections::BTreeSet;
use rust_decimal::Decimal;
use tracing::info;
use optionstratlib_math::curves::{Curve, Point2D};
use optionstratlib_math::geometrics::BiLinearInterpolation;
let curve = Curve::new(BTreeSet::from_iter(vec![
           Point2D::new(Decimal::ZERO, Decimal::ZERO),
           Point2D::new(Decimal::ONE, Decimal::TWO),
       ]));
let result = curve.bilinear_interpolate(Decimal::from(2));

match result {
    Ok(point) => info!("Interpolated point: {:?}", point),
    Err(e) => info!("Interpolation failed: {:?}", e),
}
§Errors

Returns InterpolationError::EmptyData when the grid has no samples, InterpolationError::OutOfRange when x falls outside the covered domain, InterpolationError::DegenerateInterval when the neighbouring grid cell has zero width on either axis, and InterpolationError::Bilinear when the sample is too small to build a cell from (fewer than four points on a Curve), or when a checked-arithmetic step leaves the representable range.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§

Source§

impl BiLinearInterpolation<Point2D, Decimal> for Curve

Implementation of the BiLinearInterpolation trait for the Curve struct.

§What it computes

Bilinear interpolation is defined on a cell of a two-dimensional grid: the answer is the mean of four corner samples, weighted by the query’s fractional position along each axis. A curve has one axis, so the cell is built out of two of its segments and the second fraction is fixed at one half:

  • the near edge is the segment that brackets x, from sample i to sample i + 1;
  • the far edge is the segment two positions further along, from sample f to sample f + 1;
  • the answer is the mean of the two edges read at the same fraction.
t    = (x - x[i]) / (x[i+1] - x[i])
near = y[i] + t * (y[i+1] - y[i])
far  = y[f] + t * (y[f+1] - y[f])
y    = (near + far) / 2

§Behaviour near the upper boundary

The far edge is f = i + 2 while such a segment exists. Over the last two segments of the curve it does not, and f is clamped to len - 2, the curve’s last segment. Two consequences a caller can act on:

  • on the final segment the two edges coincide, near equals far, and the answer is the linear interpolant there. Not always to the last digit: this method normalises x before scaling by the rise and LinearInterpolation::linear_interpolate scales first, so where the fraction along the segment has no finite decimal expansion the two round at scale 28 on different quantities and can part company in the 28th place;
  • on the second-to-last segment the far edge is the segment immediately after the near one instead of the one after that.

§Why not clamp the window start

The obvious repair, and the one CubicInterpolation::cubic_interpolate applies at its own boundary, is to slide the whole window back to the last four samples. Do not reach for it here. The window start is also the denominator of t, so moving it back off the segment holding x pushes t past 1. Written out, the answer weights the four ordinates it reads by

y = (1 - t)/2 * y[i] + t/2 * y[i+1] + (1 - t)/2 * y[f] + t/2 * y[f+1]

which sums to one for any t, but has every weight non-negative only while t is in [0, 1]. Past 1 the two (1 - t)/2 weights go negative, the answer stops being a convex combination of the cell’s corners, and what is left is an extrapolation of a cell that does not contain x: on (0,0), (1,1), (2,4), (3,9) the window-start clamp returns 9.5 at x = 2.5, above every ordinate the curve has. Being a convex combination of its cell’s corners is what makes bilinear interpolation bilinear interpolation, so it is the near edge that has to stay put and the far edge that gives way. That is why the two boundary rules differ.

Before this rule existed the last two segments returned InterpolationError::Bilinear because the window ran off the end.

§What this is not

The far edge sits elsewhere on the curve, so the result does not follow the samples between knots and this is not an interpolant through them: on (0,0), (1,1), (2,4), (3,9) the value at x = 0.5 is 3.5, not the 0.5 a straight reading of the bracketing segment gives.

An x landing exactly on a sample returns that sample, which neither one-sided limit approaching it matches, so the result jumps at every sample. That jump is as old as the method and owes nothing to the boundary rule above: on (0,0), (1,1), (2,4), (3,9), (4,16) both limits at x = 1 are 5 against a sample of 1, and both segments meeting there are far from the clamp. What the clamp does add is that at the last two samples before the end of the curve the two limits no longer agree with each other either, because the segments on either side get different far edges.

Reach for LinearInterpolation or SplineInterpolation when a curve through the samples is what is wanted.

§Parameters

  • x: The x-coordinate to interpolate at. Must lie within the range of the curve’s x-coordinates.

§Returns

  • Ok(Point2D): The interpolated point at x, both coordinates as Decimal.
  • Err(InterpolationError): See the error list below.

§Errors

§One point per abscissa

A curve is a function of its abscissa; see Curve::new. Asking for a repeated abscissa returns InterpolationError::DegenerateInterval, as it does for the three sibling algorithms; no ordinate of the stack is chosen. A stack elsewhere on the curve does not stop this method answering: only the near edge’s width is divided by, and a stack sitting on the far edge is read for its two ordinates alone.

§See Also

  • Curve: The overarching structure that represents the curve.
  • Point2D: The data type used to represent individual points on the curve.
  • find_bracket_points: A helper method used to locate the two points that bracket the given x-coordinate.
Source§

impl BiLinearInterpolation<Point3D, Point2D> for Surface