Skip to content

Add user-defined domain support to Minterpy polynomials#81

Merged
damar-wicaksono merged 50 commits into
devfrom
dev-50
Feb 23, 2026
Merged

Add user-defined domain support to Minterpy polynomials#81
damar-wicaksono merged 50 commits into
devfrom
dev-50

Conversation

@damar-wicaksono
Copy link
Copy Markdown
Member

@damar-wicaksono damar-wicaksono commented Feb 20, 2026

Introduces a Domain class and integrates it throughout the Minterpy codebase, allowing users to work directly with functions on custom rectangular domains $\Omega = [a_1, b_1] \times \cdots \times [a_m, b_m]$ without manually handling coordinate transformations.

Motivation

Previously, users interpolating functions on non-canonical domains had to manually transform unisolvent nodes before function evaluation, transform query points before polynomial evaluation, and track and apply chain rule / Jacobian factors for differentiation and integration. This was error-prone and cumbersome. The new Domain class encapsulates all of this transparently.

Changes

New

  • Domain class (minterpy/core/domain.py): handles affine coordinate transformations between user domains and the internal reference domain $[−1,1]^m$, with factory methods Domain.uniform() (uniform domain) and Domain.identity() (default domain, i.e., user == internal thus "identity"), coordinate maps map_to_internal() / map_from_internal(), scaling factor helpers get_diff_factor() / get_int_factor(), contains() for domain validation, and the is_identity property to skip unnecessary transformations
  • Domain-aware Grid: domain parameter in constructor (defaults to normalized domain); grid(callable) now evaluates at nodes in the user domain; grid operations (*, |) validate domain consistency
  • Domain-aware polynomial evaluation: __call__() accepts query points in the user domain and transforms them automatically
  • Domain-aware differentiation: diff() and partial_diff() apply chain rule scaling factors automatically
  • Domain-aware integration: integrate_over() applies Jacobian scaling factors automatically
  • bounds parameter in interpolate(), Interpolator, and Interpolant for specifying custom domains without manually constructing a Domain object
  • DomainMismatchError exception for mismatched domain operations

Changed

  • partial_diff() is now syntactic sugar for diff(), removing duplicate _partial_diff() static methods
  • Zero-order differentiation returns a copy (identity operation)
  • Grid generating points validation now uses Domain class instead of check_domain_fit()
  • Interpolator refactored to use attrs.define syntax
  • Polynomial differentiation and integration tests centralized into dedicated test modules

Removed

  • check_domain_fit() utility function
  • internal_domain and user_domain properties from polynomial abstract class
  • Strict bounds validation in integrate_over() (extrapolation now allowed with care)
  • minterpy.utils.polynomials.interface module

Backward Compatibility

Fully backward compatible. When no domain is specified, Domain.identity(m) is used internally and all behavior is identical to the previous implementation. The is_identity property allows skipping transformations for zero overhead in the default domain case.


Resolves: #50

damar-wicaksono and others added 30 commits January 20, 2026 23:58
Add Domain class to handle affine coordinate transformations between
user-defined rectangular domains and the normalized domain [-1,1]^m.

Features:

- Affine transformations: `map_to_normalized()`, `map_from_normalized()`
- Factory methods: `Domain.uniform()`, `Domain.normalized()`
- Scaling factors: `get_diff_factor()` (chain rule),
  `get_int_factor()` (Jacobian)
- Domain utilities: `contains()`, `partial_matching()`,
  `is_normalized`
- Optional validation with validate parameter

Additional implementation notes:

- Uses "normalized" terminology instead of "canonical" to avoid confusion
- Comprehensive test suite with 35+ tests across 9 test classes

Also adds:

- verify_domain_bounds() utility function for input validation
- `InvalidDomainBoundsError` as a new custom exception derived
  from `ValueError`

This commit should resolve Issue #51
Implement Domain class for rectangular domain transformations
Implement `__or__` dunder method to combine domains
with different dimensions via the | operator.
Add expand_dim() method to support dimension
expansion with either integer or Domain targets.

- The implementation of __or__ is delegated to expand_dim()
- Add expand_dim(int) for expanding normalized domains
- Add expand_dim(Domain) for expanding to match target domain
- Raise (a new) DomainMismatchError when domains not partially matched
- Raise ValueError for invalid dimension targets
- Add comprehensive test coverage for both operations

This commit should resolve Issue #62.
Add union operator and expand_dim method to Domain class
The Domain class has been integrated as a property
of the Grid class for dealing with custom rectangular domains
while maintaining backward compatibility with normalized
[-1, 1]^m domain.

Several key changes:

- Add optional `domain` parameter to Grid constructor
  and all factory methods
- Domain defaults to normalized `Domain.normalized(m)`
  when not specified
- Add `domain` property
- Update `__call__` to transform unisolvent_nodes
  to user domain before evaluation
- Add domain equality check to `__eq__`
  taking into account the domain
- Update `__mul__` and `__or__` to combine domains
  via domain union (|)

The test suite has been updated accordingly.

This commit should resolve Issue #52.
Integrate Domain class with Grid
Add tests on taking the union and product
of two instances of Grid with different domains
should raise DomainMismatchError.

This commit is part of the resolution of Issue #52.
Update tests for Grid for DomainMismatchError
An instance of `Domain` can now be passed as a construction
parameter of a polynomial with the following validation logic:

- When both `grid` and `domain` are None: construct default grid
  with default domain
- When only `grid` specified: use grid's existing domain
- When only `domain` specified: construct grid with specified domain
- When both specified: validate consistency,
  raise `DomainMismatchError` if mismatch

Furthermore, the `Domain` instance associated with a grid
is a read-only property of all polynomial instances.

While the particular property is not used in any underlying logic,
this addition establishes the foundation for domain-aware polynomial
operations by making domain accessible as a first-class property
while maintaining backward compatibility.

This commit should resolve Issue #66.
Add domain to the Polynomial abstract class
- Added `is_uniform` property for checking domain uniformity.
- Enhanced `is_normalized` with tolerance-based checks
  (`DEFAULT_RTOL`, `DEFAULT_ATOL`).
- Enhanced `expand_dim` to handle identity and uniform domains.
- Overhauled `__eq__` and `partial_matching` for stricter or
  more flexible comparisons, respectively.
- Improved test coverage for union and dimension expansion edge cases.
- Updated private `_bounds` attribute and introduced `bounds` as a property.
Domain verification for polynomial operations now happens implicitly
through grid operations rather than explicit pre-checks. When adding
or multiplying polynomials, domain compatibility is validated by the
underlying grid union/product operations,
which raise `DomainMismatchError` if domains are incompatible.

Key changes:

Removed explicit domain checks:
- Polynomial addition and multiplication no longer pre-verify domain
  matching before delegating to grid operations (union and product)
- Domain incompatibility is now caught by grid operations themselves

Simplified dimension handling:
- Polynomial-polynomial operations no longer assume matching dimensions
  before delegation to concrete implementations
- Dimension matching is now handled at the lower level when required
- `expand_dim()` method simplified to only support expansion to a
  target dimension (integer)

Deprecations:
- `has_matching_dimension()` method (polynomial abstract class)
- `has_matching_domain()` method (polynomial abstract class)
- `_match_dims()` private method (polynomial abstract class)
- `verify_poly_domain()` verification function (removed prior to
  deprecating `user_domain` and `internal_domain`)

New features:
- `__call__()` method now accepts optional keyword-only parameter
  `truncate_cols`: allows lower-dimensional polynomials to be evaluated
  on higher-dimensional points

All tests have been updated to reflect these changes.

This commit should resolve Issues #67 and #68.
Refactor domain handling in Grid and Polynomial operations
The logic has been improved for clarity
in checking the grid compatibility.
The corresponding test case has also been improved.
Introduced internal bounds to the `Domain` class to lay
the groundwork for deprecating the notion of `internal_domain`
directly attributed to polynomial instances.

Internal bounds are currently hard-coded to `[-1, 1]^m`
such that there is no change in behavior.
However, the infrastructure is now in place
for future generalization where a single Domain
instance manages both user and internal domain.

Changes:

- Renamed public properties for brevity and clarity:
  `lower_bounds` to `lower`, `upper_bounds` to `upper`,
  `domain_widths` to `widths`
- Added `internal_bounds` property (public, lazy evaluation)
- Add private properties: `_internal_lowers`, `_internal_uppers`,
  `_internal_widths`
- Renamed `map_to_normalized()` to `map_to_internal()`
- Renamed `map_from_normalized()` to `map_from_internal()`
- Update `map_to_internal()` and `map_from_internal()` to use
  internal bounds instead of hard-coded [-1, 1]
- Update `is_normalized` to check against internal bounds
  with numerical tolerance
- Add `internal` parameter to `contains()` for validating
  points againsts internal domain
- Update `get_int_factor()` and `get_diff_factor()` to compute
  scaling factors relative to internal bounds
- Upate `__eq__()` to check both user bounds and internal bounds
  (this remains a strict equality)
- Update `partial_matching()` to take into account
  internal domain (this remains with tolerance)
- Update tests to cover new functionality
Simplified generating points validation by leveraging
the `Domain` class's internal bound checking.
This removes `check_domain_fit()` verification function
and consolidate all validation logics into the private
method `_verify_generating_points()` of the `Grid` class.

Changes:

- Remove `check_domain_fit()` verification function
  (the core functionality is now replaced
  by `domain.contains(..., internal=True)`
- Refactor `_verify_generating_points()` method in the `Grid` class
  to handle generating points validation.
- Change the method `_verify_generating_points()` to return
  the validated generating points to be consistent with
  "validate-and-assign" pattern.
- Update the tests to reflect the changes
The concepts of user domain and internal domain have been superseded
by the `Domain` class integration. These legacy properties are no longer
needed as domain information is now contained in the instance
of the new class (attached to the instance of `Grid`).

Changes:

- Remove `internal_domain` and `user_domain` parameters from `__init__()`
- Remove abstract methods `generate_internal_domain()`
  and `generate_user_domain()`
- Remove `internal_domain` and `user_domain` from all factory methods:
  - `from_degree()`
  - `from_poly()`
  - `from_grid()`
- Refactor tests to validate `Domain` functionality
  and remove outdated domain-dependent logic.
- Clean up unused imports and deprecated methods
  like `verify_domain()`.

Breaking changes:

- Constructor for polynomials no longer accepts
  `internal_domain` or `user_domain` parameters

This commit resolves Issue #69 and partially
resolves Issue #53.
Integrate Domain class and remove legacy domain properties
Add an identity check to short-circuit equality
comparison when instances of `MultiIndexSet`
are identical.
Separate arithmetic operations into dedicated modules:

- `mp.polynomials.arithmetic.py`: Core coefficient computation logic
- `mp.polynomials.scalar_add.py`: Scalar addition for canonical,
  Newton, and Chebyshev bases

Additonal changes:

- Minimize repetitions in the concrete implementations
- Remove redundant implementation in
  `mp.utils.polynomials.interface`

This commit resolves Issue #70 and partially resolves Issue #53.
Refactor polynomial arithmetic into modular components
Add all changes related to the resolution
of Issue #53 in the Unreleased section.
Integrate Domain and (Domain-aware) Grid into Polynomial Classes
Query points passed to polynomial `__call__` are now expected in the
polynomial's user domain (default: `[-1, 1]^m`).
Points are automatically transformed to the internal domain
before evaluation, eliminating manual coordinate transformations.

The internal polynomial representation remains unchanged and continues
to use the internal domain (currently `[-1, 1]^m`).

Key changes:

- Refactor `__call__` to accept user domain points with automatic transformation
- Add `eval_on_internal()` method for direct internal evaluation
  bypassing transformation
- Rename `Domain.is_normalized` to `Domain.is_identity` for clarity
- Refactor the verification function `verify_query_points()`
  to `standardize_query_points()` with integrated column truncation
- Update polynomial evaluation tests to verify domain invariance

This commit resolves Issue #54.
Add domain-aware polynomial evaluation
Polynomial integration now supports custom rectangular domains,
automatically applying Jacobian scaling factors to account for
domain transformations.

The changes is fully backward compatible; default domain
remains in `[-1, 1]^m`.

Key changes:

- Enable integration over user-defined domains with automatic
  Jacobian scaling
- Remove strict bounds validation: integration outside domain bounds
  is allowed (extrapolation) but should be used with care
- Refactor `Domain` class: rename `get_int_factor()` to `int_factor()`;
  `is_identity` property is now lazily evaluated and cached
- Centralize integration tests into a dedicated test module

This commit resolves Issue #56.
Add domain-aware polynomial integration
The method `get_diff_factor()` in the `Domain` class has been
renamed to `diff_factor()` for brevity and consistency
with `int_factor()`.

Key changes:

- Rename `get_diff_factor()` to `diff_factor()`
- Short-circuit zero-order differentiation, i.e.,
  immediately returns a factor of 1.0 without
  any computation
- Update the relevant tests

Ref: #55
Polynomial differentiation now correctly takes into account
rectangular user domains by scaling the polynomial
with the correct differentiation factor (a function of
the given order of derivatives).

Key changes:

- Use `diff_factor()` from the `Domain` class to scale
  differentiated polynomials according to the user domain
  and derivative order
- Remove the static abstract method `_partial_diff()` from the
  polynomial abstract base class in favor of centralizing
  the operation to `diff()`
- Refactor `partial_diff()` to be syntactic sugar for `diff()`
- Remove the static concrete method `_partial_diff()` from
  `NewtonPolynomial` and `CanonicalPolynomial`
- Implement zero-order differentiation as identity operation
  (returns a copy of the polynomial)
- Expand the test suite for polynomial differentiation,
  focusing on mathematical properties
- Centralize polynomial differentiation tests into a dedicated
  test module

Refs: #50
Resolves: #55
damar-wicaksono and others added 18 commits February 13, 2026 22:58
The high-level function `interpolate()` and
classes `Interpolator` and `Interpolant` now support
custom rectangular domains via the `bounds` parameter.
Users can specify domain bounds directly without
manually creating `Domain` instances.

The underlying interpolating polynomials are
automatically constructed with domain awareness.

Key changes:

- Add `bounds` parameter to `interpolate()`,
  `Interpolant.from_degree()`, and `Interpolator()`
  to specify custom rectangular domains
- Modernize the syntax of `attrs` (e.g., `attr.ib()`)
- Update all tests to cover both default and
  custom domain cases

Ref: #50
Resolves: #57
Add domain support to interpolation module
All occurences of the factory method `Domain.normalized()` have been
replaced with `Domain.identity()` to improve naming consistency
and better reflect its role as the default internal reference domain.
The Quickstart guide in `README.md` has been updated
with the example on a custom domain to demonstrate
the new domain-aware capabilities.

Additional changes:

- Revise the opening paragraphs in `README.md` and
  the documentation landing page
- Revise the installation instructions for clarity
- Add citation information for recent publications
- Add updated plots

Ref: #58
The Quickstart Guide has been updated to include
a two-dimensional example on a non-default domain,
introducing the `bounds` argument
of the `interpolate` function.

Ref: #58
The "Arithmetic Operations with Polynomials" tutorial
has been updated removing redundant phrasing
and addressing minor prose inconsistencies.
Furthermore, notes on domain compatibility for
polynomial-polynomial operations have been added.

Ref: #58
The "1D Polynomial interpolation" tutorial has been
updated updated with better explanations,
minor inconsistency fixes,
and notes regarding the default domain.

Ref: #58
The tutorial on multidimensional polynomial interpolation
has been revised by refining explanations,
adding domain references, and updating the Franke
function to align with the custom rectangular
domain `[0, 1]^2`.

Ref: #58
The tutorial on calculus operations with polynomials
has been updated to use the Franke function on its
natural domain [0, 1]^2 and to demonstrate the domain
feature of Minterpy polynomials. The examples, code,
and descriptions have been updated accordingly.

Ref: #58
The How-To Guides on `Grid` construction have been
updated to introduce the `domain` optional parameter
and the default behavior.

Ref: #58
The How-To Guide on calling a `Grid` instance
has been updated with a custom domain.

Ref: #58
The API reference section of the documentation
has been updated with the section on `Domain`.

Ref: #58
A new page in the fundamental section has been introduced
to explain the transformation between user-defined
and internal reference domains from a theoretical perspective.

Ref: #58
A series of How-To Guides have been added to
demonstrate various features of the `Domain` class,
including creating an instance with various
factory methods, checking instance equality and
partial matching, taking the union of instances,
and computing scaling factors for differentiation
and integration.

Ref: #58
Update documentation for the domain feature
@damar-wicaksono damar-wicaksono added documentation Improvements or additions to documentation enhancement New feature or request refactoring Cleaning up / re-implementation without any functional or top-level behavioral changes labels Feb 20, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Documentation preview (without executed notebooks) is available.

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 20, 2026

Codecov Report

❌ Patch coverage is 97.43590% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/minterpy/utils/verification.py 71.79% 11 Missing ⚠️
src/minterpy/interpolation.py 96.20% 3 Missing ⚠️
src/minterpy/core/grid.py 98.88% 1 Missing ⚠️
Files with missing lines Coverage Δ
...terpy/core/ABC/multivariate_polynomial_abstract.py 88.84% <100.00%> (+3.65%) ⬆️
src/minterpy/core/__init__.py 100.00% <100.00%> (ø)
src/minterpy/core/domain.py 100.00% <100.00%> (ø)
src/minterpy/core/multi_index.py 100.00% <100.00%> (ø)
.../minterpy/extras/regression/ordinary_regression.py 99.53% <100.00%> (ø)
src/minterpy/global_settings.py 100.00% <100.00%> (ø)
src/minterpy/jit_compiled/common.py 100.00% <ø> (ø)
src/minterpy/jit_compiled/multi_index.py 96.90% <ø> (ø)
src/minterpy/jit_compiled/newton/eval.py 100.00% <ø> (ø)
src/minterpy/polynomials/arithmetic.py 100.00% <100.00%> (ø)
... and 10 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions
Copy link
Copy Markdown
Contributor

Documentation preview (without executed notebooks) is available.

The DOI badge provided by RODARE seems not to be
rendered correctly. The one from shields.io
is used instead for now; the link still
goes to RODARE.
@github-actions
Copy link
Copy Markdown
Contributor

Documentation preview (without executed notebooks) is available.

@damar-wicaksono damar-wicaksono merged commit 80839aa into dev Feb 23, 2026
11 checks passed
github-actions Bot added a commit that referenced this pull request Feb 23, 2026
@damar-wicaksono damar-wicaksono deleted the dev-50 branch February 23, 2026 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request refactoring Cleaning up / re-implementation without any functional or top-level behavioral changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add user-defined domain support for Minterpy polynomials

1 participant