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!
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$\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:
Domainclass encapsulates all required logic between a custom rectangular domainGrid, all concrete polynomial types,interpolate()function, andOrdinaryRegression.Consider the function:
where$x_1, x_2 \in [0, 1]$ .
To interpolate this function using
interpolate(), user-defined bounds can now be specified:An instance of the
Domainclass with the specified bounds will be created and attached to the underlying polynomial.To create the polynomial from scratch, an instance of the
Domainclass for the above domain can be constructed via a factory method:The instance can either be used as part of
Gridconstructionor directly to one of the concrete polynomials:
In either case, the instance domain will eventually be directly associated with the underlying grid.
The mental model of Minterpy polynomials now becomes:
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 toCorrect scaling for differentiation and integration
poly.diff()andpoly.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 approximationFurthermore,$Q_f$ over the user domain $\Omega$ , instead of the integral of the internal polynomial $Q$ over $[-1, 1]^m$ .
poly.integrate_over()now applies the scaling factor (i.e., determinant of the Jacobian) automatically. The result is the integral of the polynomial approximationIf 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
Domainclass, the parametersuser_domainandinternal_domainof the polynomial default constructor have been removed in favor of a parameterdomain. 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
OrdinaryRegressionhas been changed;origin_polynow is the last parameter afterdomain.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 aDomaininstance.If
user_domainorinternal_domainwere passed to a polynomial constructor (noting that these parameters had no effect in previous releases), replace these withdomain. IfOrdinaryRegressionwas constructed with positional arguments, the call site should be updated to account fororigin_polymoving 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!