Skip to content

Decide if a new release is warranted #86

@damar-wicaksono

Description

@damar-wicaksono

By adding domain feature the polynomials, I would argue that a new release (v0.4.0) can be made.

Consider the following release notes draft:


Minterpy v0.4.0: Domain-aware polynomials

So far, users working with functions on domains other than $[-1, 1]^m$ had to manually transform coordinates, track the scaling factors for integration (i.e., the determinant of the Jacobian) and differentiation (i.e., via chain rule). This procedure is a nuisance at best and error prone at worst.

Version 0.4.0 resolves this by introducing domain-aware polynomials.

New features

User-defined domains

The new Domain class encapsulates all required logic between a custom rectangular domain $\Omega = [a_1, b_1] \times \cdots \times [a_m, b_m]$ and the internal domain $[-1, 1]^m$. It propagates through the object hierarchy: Grid, all concrete polynomial types, interpolate() function, and OrdinaryRegression.

Consider the function:

$$ f(x_1, x_2) = \cos{(x_1 + x_2)} e^{x_1 x_2}, $$

where $x_1, x_2 \in [0, 1]$.

To interpolate this function using interpolate(), user-defined bounds can now be specified:

import minterpy as mp

def func(xx):
  return np.cos(np.sum(xx, axis=1)) * np.exp(np.prod(xx, axis=1))

interpol = mp.interpolate(
  func,
  spatial_dimension=2,
  poly_degree=5,
  lp_degree=2.0,
  bounds=[[0, 1], [0, 1]],  # ArrayLike, lower and upper bounds per dimension
)

An instance of the Domain class with the specified bounds will be created and attached to the underlying polynomial.

To create the polynomial from scratch, an instance of the Domain class for the above domain can be constructed via a factory method:

dom = mp.Domain.uniform(spatial_dimension=2, lower=0, upper=1)

The instance can either be used as part of Grid construction

grd = mp.Grid(..., domain=dom)

or directly to one of the concrete polynomials:

poly = mp.NewtonPolynomial(..., domain=dom)

In either case, the instance domain will eventually be directly associated with the underlying grid.

The mental model of Minterpy polynomials now becomes:

$$ f(\boldsymbol{x}) \approx Q_f(\boldsymbol{x}) = Q \circ \mathcal{T} (\boldsymbol{x}), $$

where $Q_f$ is the approximating polynomial of $f$, $\mathcal{T}$ is the affine separable map $\mathcal{T} = (\mathcal{T}_1, \ldots, \mathcal{T}_m)$, $\mathcal{T}_i: [a_i, b_i] \rightarrow [-1, 1]$, and $Q$ is the internal polynomial representation that remains to be defined in $[-1, 1]^m$.

Transparent evaluation

poly(xx) now accepts query points in the user domain. Coordinate transformation to $[-1, 1]^m$ prior to the evaluation by the internal polynomial happens automatically before evaluation. If a user-defined domain is not specified or the user-defined domain is the same as the internal domain (i.e., the default domain), the transformation is skipped entirely.

Correct scaling for differentiation and integration

poly.diff() and poly.partial_diff() now apply the chain rule factors automatically to the differentiated internal polynomial. The returned polynomial thus represents the true derivative of the polynomial approximation $Q_f$ on $\Omega$, and not the derivative of the internal polynomial $Q$ on $[-1, 1]^m$.

Furthermore, poly.integrate_over() now applies the scaling factor (i.e., determinant of the Jacobian) automatically. The result is the integral of the polynomial approximation $Q_f$ over the user domain $\Omega$, instead of the integral of the internal polynomial $Q$ over $[-1, 1]^m$.

If a user-defined domain is not specified or the user-defined domain is the same as the internal domain (i.e., the default domain), the scaling factor is set to $1.0$.

Fully backward compatible

Domain and bound specifications are optional. If they are not specified, the internal reference domain of $[-1, 1]^m$ is assumed to be the user domain and all behavior is identical to the previous release.

Deprecations and breaking changes

With the introduction of the Domain class, the parameters user_domain and internal_domain of the polynomial default constructor have been removed in favor of a parameter domain. Previously, the two former parameters appeared in the signature and were processed for consistency, but never used anywhere else in the codebase.

The parameter ordering in OrdinaryRegression has been changed; origin_poly now is the last parameter after domain.

Migration guide

For users who have been applying domain transformation manually to conform with their function domain (e.g., transforming nodes before function evaluation, multiplying integration results with interval widths, or scaling the derivative outputs), bounds can now be directly passed to interpolate() function or used to construct a Domain instance.

If user_domain or internal_domain were passed to a polynomial constructor (noting that these parameters had no effect in previous releases), replace these with domain. If OrdinaryRegression was constructed with positional arguments, the call site should be updated to account for origin_poly moving to the last position.


For a detailed overview of all changes, refer to CHANGELOG.


Hi 👋 @szabo137 : What do you think? There may be additional minor tweaks to documentation and tests (Numpy deprecation issues and warnings) but I think the changes so far (albeit a single feature) might warrant a new release. Maybe you can have a look? Thanks a lot!

Metadata

Metadata

Assignees

No one assigned

    Labels

    releaseIssues related to a Minterpy release

    Type

    No fields configured for Task.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions