Skip to content

Commit 430cbeb

Browse files
authored
Merge pull request #14 from 0xC000005/v0.17-integrate
v0.17.0: Integrate Everywhere — Slider + TT integrate()
2 parents 50a6ccd + 11536a6 commit 430cbeb

12 files changed

Lines changed: 1268 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ All notable changes to PyChebyshev will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.17.0] - 2026-04-25
9+
10+
### Added — Integrate Everywhere
11+
12+
- `ChebyshevSlider.integrate(dims=None, bounds=None)` — full and partial integration via the sliding-decomposition closed form. Returns a scalar on full integration; a `ChebyshevSlider` over surviving dims on partial.
13+
- `ChebyshevTT.integrate(dims=None, bounds=None)` — full and partial integration via Fejér-1 quadrature contraction into each integrated core's node axis. Returns a scalar on full integration; a `ChebyshevTT` over surviving dims on partial. Works on TT objects built via Cross, SVD, and ALS methods.
14+
- New private helpers in `_calculus.py`: `_slider_partition_intersect()`, `_integrate_tt_along_dim()`.
15+
- Extended user-guide page `docs/user-guide/calculus.md` with v0.17 Slider/TT integration sections.
16+
17+
After v0.17, all four PyChebyshev classes support integration. Roots/min/max on Slider/TT remain deferred to v0.21.
18+
19+
**Beyond MoCaX:** MoCaX 4.3.1 has no `integrate()` API on any class.
20+
821
## [0.16.0] - 2026-04-25
922

1023
### Added — Polish Bundle (final MoCaX 4.3.1 cosmetic mirror)

CLAUDE.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ PyChebyshev is a pip-installable Python library for multi-dimensional Chebyshev
1212
# Setup
1313
uv sync
1414

15-
# Run tests (~838 tests, ~110s due to 5D Black-Scholes builds)
15+
# Run tests (~890 tests, ~110s due to 5D Black-Scholes builds)
1616
uv run pytest tests/ -v
1717

1818
# Run a single test
@@ -59,6 +59,9 @@ The installable package. Public classes: `ChebyshevApproximation`, `ChebyshevSpl
5959
`is_dimensionality_allowed()` static, `defer_build=True` +
6060
`set_original_function_values()`, and optional `Domain`/`Ns`/`SpecialPoints`
6161
typed helpers (constructors accept both forms).
62+
- v0.17 adds `integrate()` on `ChebyshevSlider` and `ChebyshevTT` (full +
63+
partial integration). After v0.17, every PyChebyshev class supports
64+
integration. Roots/min/max on Slider/TT remain deferred to v0.21.
6265

6366
### Benchmark Scripts (project root)
6467

@@ -77,6 +80,7 @@ Not part of the library. Compare Chebyshev barycentric against alternative metho
7780
- `compare_from_values.py` — PyChebyshev nodes()/from_values() vs MoCaX Extend comparison (requires `mocaxextend_lib/`)
7881
- `compare_special_points.py` — PyChebyshev special_points vs MoCaX MocaxSpecialPoints + MocaxNs comparison (requires `mocax_lib/`)
7982
- `compare_v016_polish.py` — PyChebyshev v0.16 polish surface vs MoCaX 4.3.1 cosmetic API (requires `mocaxpy`; gracefully skips MoCaX side if not installed)
83+
- `compare_calculus_completion.py` — PyChebyshev v0.17 Slider/TT integrate vs MoCaX 4.3.1 (no equivalent — beyond-MoCaX feature)
8084

8185
### Tests (`tests/`)
8286

@@ -101,6 +105,9 @@ Not part of the library. Compare Chebyshev barycentric against alternative metho
101105
`get_evaluation_points`, `get_num_evaluation_points`), `peek_format_version`,
102106
`is_dimensionality_allowed`, `defer_build` + `set_original_function_values`,
103107
`Domain`/`Ns`/`SpecialPoints` typed helpers.
108+
- `test_calculus_completion.py`~37 tests: `ChebyshevSlider.integrate()` (full
109+
and partial), `ChebyshevTT.integrate()` (full and partial), cross-class
110+
consistency checks, bounds validation.
104111

105112
### CI/CD (`.github/workflows/`)
106113

compare_calculus_completion.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Side-by-side comparison: PyChebyshev v0.17 integrate() on Slider/TT vs MoCaX 4.3.1.
2+
3+
MoCaX has no integrate() API on any class. This script demonstrates
4+
PyChebyshev's spectral integration as a beyond-MoCaX feature.
5+
6+
uv run python compare_calculus_completion.py
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import math
12+
13+
from pychebyshev import ChebyshevSlider, ChebyshevTT
14+
15+
16+
def f(x, _):
17+
return math.sin(x[0]) + math.cos(x[1])
18+
19+
20+
def main():
21+
domain = [[-1.0, 1.0], [-1.0, 1.0]]
22+
23+
# PyChebyshev Slider integrate
24+
slider = ChebyshevSlider(
25+
f, 2, domain, [10, 10],
26+
partition=[[0], [1]], pivot_point=[0.0, 0.0],
27+
)
28+
slider.build(verbose=False)
29+
print("PyChebyshev v0.17 — integrate() on all four classes:")
30+
print(f" Slider full integrate → {slider.integrate():.6f}")
31+
print(f" Slider partial (dims=[0]) → {type(slider.integrate(dims=[0])).__name__}")
32+
33+
# PyChebyshev TT integrate
34+
tt = ChebyshevTT(f, 2, domain, [10, 10])
35+
tt.build(verbose=False)
36+
print(f" TT full integrate → {tt.integrate():.6f}")
37+
print(f" TT partial (dims=[0]) → {type(tt.integrate(dims=[0])).__name__}")
38+
39+
# Analytical answer for sin(x) + cos(y) over [-1,1]^2 = 4*sin(1)
40+
print(f"\n Analytical: 4 * sin(1) → {4.0 * math.sin(1.0):.6f}")
41+
42+
# MoCaX equivalent: none. Document this clearly.
43+
print("\nMoCaX 4.3.1: no integrate() API on any class — PyChebyshev is unique here.")
44+
45+
46+
if __name__ == "__main__":
47+
main()

docs/roadmap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ breaking changes.
132132

133133
**Closes MoCaX gaps:** the last cosmetic surface methods.
134134

135-
## v0.17 — Integrate Everywhere :material-clock-outline:
135+
## v0.17 — Integrate Everywhere :material-check:
136136

137137
Extend the v0.9 calculus toolkit so every class supports integration.
138138

docs/user-guide/calculus.md

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,20 @@ all from a single pre-built proxy, without calling the pricing engine again.
2424
See [Chebyshev Algebra](algebra.md) for combining interpolants and
2525
[Extrusion & Slicing](extrude-slice.md) for dimension manipulation.
2626

27+
!!! info "v0.17"
28+
`integrate()` now works on all four classes. `ChebyshevSlider` and
29+
`ChebyshevTT` support full and partial integration (see
30+
[Slider integration](#slider-integration-v017) and
31+
[TT integration](#tt-integration-v017)).
32+
2733
## Supported Classes
2834

2935
| Class | `integrate()` | `roots()` | `minimize()` / `maximize()` |
3036
|-------|:---:|:---:|:---:|
3137
| `ChebyshevApproximation` | Yes | Yes | Yes |
3238
| `ChebyshevSpline` | Yes | Yes | Yes |
33-
| `ChebyshevSlider` | No | No | No |
34-
| `ChebyshevTT` | No | No | No |
39+
| `ChebyshevSlider` | Yes (v0.17) | No | No |
40+
| `ChebyshevTT` | Yes (v0.17) | No | No |
3541

3642
## Integration
3743

@@ -200,6 +206,83 @@ cheb_2d.build()
200206
result = cheb_2d.integrate(dims=[0, 1], bounds=[(0.0, 1.0), None])
201207
```
202208

209+
### Slider Integration (v0.17)
210+
211+
`ChebyshevSlider` uses the closed-form structure of the sliding decomposition.
212+
For a slider with pivot value $pv$ and slides $s_i$ over groups $G_i$:
213+
214+
$$
215+
f(x) \approx pv + \sum_i \bigl[s_i(x_{G_i}) - pv\bigr]
216+
$$
217+
218+
Integrating over a region $T = \prod_d [a'_d, b'_d]$ gives:
219+
220+
$$
221+
\int_T f \, dx = pv \cdot \mathrm{vol}(T) \cdot (1 - k)
222+
+ \sum_i \mathrm{vol}(T \setminus G_i) \cdot \int_{T \cap G_i} s_i
223+
$$
224+
225+
where $k$ is the number of slides. Each slide integral is delegated to
226+
`ChebyshevApproximation.integrate()`.
227+
228+
Full integration returns a scalar. Partial integration (some dims surviving)
229+
returns a new `ChebyshevSlider` over the surviving dimensions. Slides whose
230+
groups are fully integrated out are absorbed into an updated pivot value;
231+
slides with partially surviving groups are reduced by integrating the consumed
232+
dimensions of their underlying approximation.
233+
234+
```python
235+
from pychebyshev import ChebyshevSlider
236+
import math
237+
238+
slider = ChebyshevSlider(
239+
lambda x, _: math.sin(x[0]) + math.cos(x[1]),
240+
2, [[-1.0, 1.0], [-1.0, 1.0]], [12, 12],
241+
partition=[[0], [1]], pivot_point=[0.0, 0.0],
242+
)
243+
slider.build()
244+
245+
total = slider.integrate() # → scalar
246+
reduced = slider.integrate(dims=[0]) # → 1-D ChebyshevSlider
247+
```
248+
249+
Sub-interval bounds work the same as for `ChebyshevApproximation`:
250+
251+
```python
252+
# Integrate dim 0 over [0, 1], return 1-D slider in dim 1
253+
partial = slider.integrate(dims=[0], bounds=[(0.0, 1.0)])
254+
```
255+
256+
### TT Integration (v0.17)
257+
258+
`ChebyshevTT` cores are stored in Chebyshev coefficient space. For each
259+
integrated dimension $d$, the algorithm:
260+
261+
1. Converts core $d$ from coefficient to value space (type-I DCT).
262+
2. Contracts the Fejér-1 quadrature weights (or sub-interval weights for
263+
bounded integration) into the node axis, producing a rank-2 matrix.
264+
3. Absorbs that matrix into the nearest surviving core to maintain the TT chain.
265+
266+
Full integration contracts all cores to a scalar. Partial integration
267+
returns a new `ChebyshevTT` over the surviving dimensions. The method works
268+
for TT objects built with any build strategy (`'cross'`, `'svd'`, `'als'`).
269+
270+
```python
271+
from pychebyshev import ChebyshevTT
272+
import math
273+
274+
tt = ChebyshevTT(
275+
lambda x, _: math.sin(x[0]) + math.cos(x[1]),
276+
2, [[-1.0, 1.0], [-1.0, 1.0]], [12, 12],
277+
)
278+
tt.build()
279+
280+
total = tt.integrate() # → scalar
281+
reduced = tt.integrate(dims=[0]) # → 1-D ChebyshevTT
282+
```
283+
284+
Sub-interval bounds are supported identically to `ChebyshevApproximation`.
285+
203286
### `integrate()` API
204287

205288
| Parameter | Type | Description |
@@ -208,8 +291,8 @@ result = cheb_2d.integrate(dims=[0, 1], bounds=[(0.0, 1.0), None])
208291
| `bounds` | `tuple`, `list[tuple/None]`, or `None` | Sub-interval bounds. `None` = full domain. Single `(lo, hi)` for one dim, or list with positional correspondence to `dims`. |
209292

210293
**Returns**: `float` if all dimensions are integrated; otherwise a
211-
lower-dimensional interpolant of the same type (`ChebyshevApproximation` or
212-
`ChebyshevSpline`).
294+
lower-dimensional interpolant of the **same type** (`ChebyshevApproximation`,
295+
`ChebyshevSpline`, `ChebyshevSlider`, or `ChebyshevTT`).
213296

214297
**Errors**:
215298

@@ -357,9 +440,10 @@ Partial integration on a spline returns a lower-dimensional `ChebyshevSpline`.
357440

358441
## Derivatives, Error Estimation, and Serialization
359442

360-
**Partial integration** returns a fully functional interpolant (either
361-
`ChebyshevApproximation` or `ChebyshevSpline`). All existing features
362-
work on the result:
443+
**Partial integration** returns a fully functional interpolant of the same
444+
type (`ChebyshevApproximation`, `ChebyshevSpline`, `ChebyshevSlider`, or
445+
`ChebyshevTT`). For `ChebyshevApproximation` and `ChebyshevSpline`, all
446+
existing features work on the result:
363447

364448
- **Derivatives**: `vectorized_eval(point, [1])` computes derivatives in
365449
the surviving dimensions via the spectral differentiation matrices.
@@ -400,10 +484,10 @@ matrices, and barycentric evaluation.
400484

401485
## Limitations
402486

403-
- **Not yet supported on `ChebyshevSlider` or `ChebyshevTT`.**
404-
Calculus on Tensor Train interpolants would require TT-specific
405-
coefficient extraction; slider calculus would need per-slide integration
406-
with additive recombination.
487+
- **`roots()`, `minimize()`, and `maximize()` are not yet supported on
488+
`ChebyshevSlider` or `ChebyshevTT`.** These operations require
489+
1-D polynomial coefficient extraction; generalising to the sliding
490+
decomposition or TT format is deferred.
407491
- **Multi-D rootfinding** (2D Bezout resultants) is not implemented. Only
408492
1-D slices are supported via the `dim` + `fixed` interface.
409493
- **Result has `function=None`** -- partial integration results cannot call

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "pychebyshev"
7-
version = "0.16.0"
7+
version = "0.17.0"
88
description = "Fast multi-dimensional Chebyshev tensor interpolation with analytical derivatives"
99
readme = "README.md"
1010
license = {text = "MIT"}

src/pychebyshev/_calculus.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,52 @@ def _validate_calculus_args(ndim, dim, fixed, domain):
337337
slice_params.append((d, v))
338338

339339
return dim, slice_params
340+
341+
342+
def _slider_partition_intersect(
343+
group_dims: list[int], integrate_dims: list[int]
344+
) -> tuple[str, list[int]]:
345+
"""Classify a slide group against an integration set.
346+
347+
Parameters
348+
----------
349+
group_dims : list[int]
350+
Dimensions covered by the slide group.
351+
integrate_dims : list[int]
352+
Dimensions being integrated over.
353+
354+
Returns
355+
-------
356+
kind : str
357+
One of: "full" (entire group integrated), "partial" (some dims
358+
integrated), "none" (no group dims integrated).
359+
kept : list[int]
360+
Dimensions of the group NOT being integrated. Empty for "full".
361+
"""
362+
group_set = set(group_dims)
363+
integrate_set = set(integrate_dims)
364+
overlap = group_set & integrate_set
365+
if not overlap:
366+
return "none", list(group_dims)
367+
if overlap == group_set:
368+
return "full", []
369+
return "partial", [d for d in group_dims if d not in integrate_set]
370+
371+
372+
def _integrate_tt_along_dim(core: np.ndarray, weights: np.ndarray) -> np.ndarray:
373+
"""Contract a single TT core along its node axis with quadrature weights.
374+
375+
Parameters
376+
----------
377+
core : np.ndarray
378+
Shape ``(r_left, n, r_right)``.
379+
weights : np.ndarray
380+
Shape ``(n,)`` — quadrature weights scaled to the dim's domain.
381+
382+
Returns
383+
-------
384+
np.ndarray
385+
Shape ``(r_left, r_right)`` — the contracted matrix
386+
``M = sum_j core[:, j, :] * weights[j]``.
387+
"""
388+
return np.einsum("rjs,j->rs", core, weights)

src/pychebyshev/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.16.0"
1+
__version__ = "0.17.0"

0 commit comments

Comments
 (0)