multiple-integrate 2.0.0__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/PKG-INFO +46 -20
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/README.md +36 -11
- multiple_integrate-2.1.0/docs/Makefile +12 -0
- multiple_integrate-2.1.0/docs/api/core.rst +12 -0
- multiple_integrate-2.1.0/docs/api/index.rst +15 -0
- multiple_integrate-2.1.0/docs/api/public.rst +8 -0
- multiple_integrate-2.1.0/docs/api/regions.rst +7 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/docs/api.md +11 -26
- multiple_integrate-2.1.0/docs/concepts/ranges-and-regions.rst +136 -0
- multiple_integrate-2.1.0/docs/conf.py +49 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/docs/contributing.md +7 -7
- multiple_integrate-2.1.0/docs/decomposition.md +39 -0
- multiple_integrate-2.1.0/docs/examples/regions.rst +204 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/docs/examples.md +10 -10
- multiple_integrate-2.1.0/docs/index.rst +53 -0
- multiple_integrate-2.1.0/docs/installation.rst +43 -0
- multiple_integrate-2.1.0/docs/quickstart.rst +89 -0
- multiple_integrate-2.1.0/docs/strategies.md +37 -0
- multiple_integrate-2.1.0/docs/testing.md +33 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/docs/theory.md +19 -8
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/notebooks/multiple_integration.ipynb +1066 -245
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/pyproject.toml +12 -13
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/src/multiple_integrate/__init__.py +2 -2
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/src/multiple_integrate/core.py +857 -738
- multiple_integrate-2.1.0/src/multiple_integrate/regions.py +1588 -0
- multiple_integrate-2.1.0/tests/test_assumptions_api.py +87 -0
- multiple_integrate-2.1.0/tests/test_dispatch_safety.py +141 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_multiple_integrate.py +116 -112
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_reference_examples.py +1 -1
- multiple_integrate-2.0.0/tests/test_regions_misclassification.py → multiple_integrate-2.1.0/tests/test_region_classification_boundaries.py +4 -2
- multiple_integrate-2.1.0/tests/test_region_invariants_and_matrix.py +193 -0
- multiple_integrate-2.1.0/tests/test_symbolic_conditional_convergence.py +47 -0
- multiple_integrate-2.1.0/tests/test_translated_regions.py +89 -0
- multiple_integrate-2.1.0/tests/test_validation_and_timeouts.py +140 -0
- multiple_integrate-2.0.0/docs/changelog.md +0 -125
- multiple_integrate-2.0.0/docs/decomposition.md +0 -239
- multiple_integrate-2.0.0/docs/index.md +0 -163
- multiple_integrate-2.0.0/docs/strategies.md +0 -138
- multiple_integrate-2.0.0/docs/testing.md +0 -123
- multiple_integrate-2.0.0/src/multiple_integrate/regions.py +0 -1125
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/.gitignore +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/LICENSE +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/__init__.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/helpers.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_boole_piecewise.py +0 -0
- /multiple_integrate-2.0.0/tests/test_regression_constants_and_caching.py → /multiple_integrate-2.1.0/tests/test_constants_and_caching.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_coordinate_changes.py +0 -0
- /multiple_integrate-2.0.0/tests/test_disk_radial_false_positive.py → /multiple_integrate-2.1.0/tests/test_disk_radial_classification.py +0 -0
- /multiple_integrate-2.0.0/tests/test_performance_regressions.py → /multiple_integrate-2.1.0/tests/test_performance_guards.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_reference_examples_notebook.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_region_order_sympy_convention.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_advanced_shapes.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_boxes_and_simplex.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_classification_priority.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_disk_and_ball.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_graph_and_simplex.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_graph_reversal.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_interactions.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_moments_box_simplex.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_moments_disk_ball.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_parse.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_radial.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_symmetry.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_simplex_dirichlet_engine.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_singular_and_divergent_cases.py +0 -0
- {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_supported_families.py +0 -0
- /multiple_integrate-2.0.0/tests/test_regression_symmetry_and_inner_1d.py → /multiple_integrate-2.1.0/tests/test_symmetry_and_inner_integrals.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: multiple-integrate
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Symbolic multiple integration for Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/BhuvaneshBhatt/multiple_integrate
|
|
6
|
-
Project-URL: Documentation, https://
|
|
6
|
+
Project-URL: Documentation, https://multiple-integrate.readthedocs.io/
|
|
7
7
|
Project-URL: Source, https://github.com/BhuvaneshBhatt/multiple_integrate
|
|
8
8
|
Project-URL: Issue Tracker, https://github.com/BhuvaneshBhatt/multiple_integrate/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/BhuvaneshBhatt/multiple_integrate/blob/main/docs/changelog.md
|
|
@@ -16,34 +16,35 @@ Classifier: Intended Audience :: Education
|
|
|
16
16
|
Classifier: Intended Audience :: Science/Research
|
|
17
17
|
Classifier: Operating System :: OS Independent
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
23
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
23
24
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.10
|
|
25
26
|
Requires-Dist: sympy>=1.12
|
|
26
27
|
Provides-Extra: all
|
|
27
28
|
Requires-Dist: ipykernel>=6.0; extra == 'all'
|
|
28
29
|
Requires-Dist: jupyter>=1.0; extra == 'all'
|
|
29
30
|
Requires-Dist: matplotlib>=3.7; extra == 'all'
|
|
30
|
-
Requires-Dist: mkdocs-material>=9.5; extra == 'all'
|
|
31
|
-
Requires-Dist: mkdocs>=1.5; extra == 'all'
|
|
32
31
|
Requires-Dist: mypy>=1.10; extra == 'all'
|
|
32
|
+
Requires-Dist: myst-parser<5,>=4; extra == 'all'
|
|
33
33
|
Requires-Dist: notebook>=7.0; extra == 'all'
|
|
34
|
-
Requires-Dist: pymdown-extensions>=10.7; extra == 'all'
|
|
35
34
|
Requires-Dist: pytest-timeout>=2.2; extra == 'all'
|
|
36
35
|
Requires-Dist: pytest>=8; extra == 'all'
|
|
37
36
|
Requires-Dist: ruff>=0.4; extra == 'all'
|
|
37
|
+
Requires-Dist: sphinx-rtd-theme<4,>=3; extra == 'all'
|
|
38
|
+
Requires-Dist: sphinx<9,>=8; extra == 'all'
|
|
38
39
|
Provides-Extra: dev
|
|
39
40
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
40
41
|
Requires-Dist: pytest-timeout>=2.2; extra == 'dev'
|
|
41
42
|
Requires-Dist: pytest>=8; extra == 'dev'
|
|
42
43
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
43
44
|
Provides-Extra: docs
|
|
44
|
-
Requires-Dist:
|
|
45
|
-
Requires-Dist:
|
|
46
|
-
Requires-Dist:
|
|
45
|
+
Requires-Dist: myst-parser<5,>=4; extra == 'docs'
|
|
46
|
+
Requires-Dist: sphinx-rtd-theme<4,>=3; extra == 'docs'
|
|
47
|
+
Requires-Dist: sphinx<9,>=8; extra == 'docs'
|
|
47
48
|
Provides-Extra: notebook
|
|
48
49
|
Requires-Dist: ipykernel>=6.0; extra == 'notebook'
|
|
49
50
|
Requires-Dist: jupyter>=1.0; extra == 'notebook'
|
|
@@ -126,11 +127,32 @@ So a triangular integral should be written as
|
|
|
126
127
|
multiple_integrate(1, (y, 0, 1 - x), (x, 0, 1))
|
|
127
128
|
```
|
|
128
129
|
|
|
129
|
-
not with the structural outer-to-inner ordering used, for example, by Mathematica. The same convention is used by
|
|
130
|
-
`region_from_ranges(...)` when regions are recognized from dependent bounds.
|
|
130
|
+
not with the structural outer-to-inner ordering used, for example, by Mathematica. The same convention is used by `multiple_integrate`; standalone `region_from_ranges(...)` can also inspect an explicitly requested structural order for geometric classification.
|
|
131
131
|
|
|
132
132
|
This matters especially for triangular, disk, and ball examples with dependent bounds: write the tuples in the same order you would pass them to `sympy.integrate`.
|
|
133
133
|
|
|
134
|
+
When bounds contain symbolic parameters, specialized geometric formulas are used only when their orientation can be established from the supplied assumptions. This preserves the signed semantics of iterated integration.
|
|
135
|
+
|
|
136
|
+
## Assumptions
|
|
137
|
+
|
|
138
|
+
Parameter assumptions can be supplied with ordinary SymPy relations or ``Q`` predicates:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
import sympy as sp
|
|
142
|
+
from multiple_integrate import multiple_integrate
|
|
143
|
+
|
|
144
|
+
x, a = sp.symbols("x a", real=True)
|
|
145
|
+
|
|
146
|
+
multiple_integrate(
|
|
147
|
+
sp.exp(-a*x),
|
|
148
|
+
(x, 0, sp.oo),
|
|
149
|
+
assumptions={a > 0},
|
|
150
|
+
)
|
|
151
|
+
# 1/a
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
A single condition such as ``assumptions=a > 0`` is also accepted, as are lists, tuples, sets, and SymPy predicates such as ``Q.positive(a)``. Contradictory assumptions raise ``ValueError``.
|
|
155
|
+
|
|
134
156
|
---
|
|
135
157
|
|
|
136
158
|
## Representative multiple-integral examples
|
|
@@ -276,21 +298,25 @@ Here the inner function is `g(x, y) = x + y`. Its level sets in the first quadra
|
|
|
276
298
|
|
|
277
299
|
## Region model
|
|
278
300
|
|
|
279
|
-
The solver
|
|
301
|
+
The solver normalizes input bounds into explicit region objects before applying structural shortcuts.
|
|
280
302
|
|
|
281
|
-
|
|
303
|
+
Region support includes:
|
|
282
304
|
|
|
283
305
|
- `BoxRegion` for product domains with independent bounds
|
|
284
306
|
- `IteratedRegion` for general nested bounds
|
|
285
307
|
- `SimplexRegion` for standard simplex-style regions
|
|
286
308
|
- `AffineSimplexRegion` for affine images of standard simplices
|
|
287
309
|
- `GraphRegion` for simple affine graph-bounded 2D regions
|
|
288
|
-
- `DiskRegion` for
|
|
289
|
-
- `BallRegion` for
|
|
290
|
-
- `EllipsoidRegion` for axis-aligned
|
|
310
|
+
- `DiskRegion` for centered or translated axis-aligned disks
|
|
311
|
+
- `BallRegion` for centered or translated axis-aligned balls
|
|
312
|
+
- `EllipsoidRegion` for centered or translated axis-aligned ellipsoids
|
|
291
313
|
- `AnnulusRegion` for centered annuli
|
|
292
314
|
- `SphericalShellRegion` for centered spherical shells
|
|
293
|
-
- `UnionRegion` for finite unions of supported regions
|
|
315
|
+
- `UnionRegion` for finite disjoint unions of supported regions with matching ordered variables
|
|
316
|
+
|
|
317
|
+
Region constructors validate structural invariants such as distinct SymPy variables, compatible dimensions, nonnegative radii, positive ellipsoid semi-axes, and consistent nested dependencies. `UnionRegion` has additive **disjoint-union** semantics: pieces must share the same ordered variables and be disjoint up to measure-zero boundaries. Obvious overlaps among supported concentric radial pieces are rejected; if symbolic overlap cannot be decided, callers remain responsible for the disjointness claim.
|
|
318
|
+
|
|
319
|
+
Translated disks, balls, and axis-aligned ellipsoids are recognized directly from shifted Cartesian bounds. Their `center` coordinates are stored in the same order as `region.variables`, and polynomial/radial shortcuts operate relative to that center.
|
|
294
320
|
|
|
295
321
|
This improves:
|
|
296
322
|
|
|
@@ -336,7 +362,7 @@ However, it is still **not** a full symbolic region engine. In particular, it do
|
|
|
336
362
|
|
|
337
363
|
- general geometric region rewriting
|
|
338
364
|
- automatic order reversal for arbitrary dependent bounds
|
|
339
|
-
- a full region algebra
|
|
365
|
+
- a full region algebra
|
|
340
366
|
- unrestricted automatic polar/spherical coordinate changes
|
|
341
367
|
- arbitrary semialgebraic cell decomposition
|
|
342
368
|
|
|
@@ -391,6 +417,6 @@ GPL-3.0-or-later
|
|
|
391
417
|
|
|
392
418
|
---
|
|
393
419
|
|
|
394
|
-
##
|
|
420
|
+
## Limitations
|
|
395
421
|
|
|
396
|
-
|
|
422
|
+
The package includes exact simplex / Dirichlet formulas and coordinate changes for selected disks, balls, shells, and ellipsoids, but it does **not** attempt completely general geometric rewriting or arbitrary symbolic substitutions. It is best viewed as a recognition-driven exact integrator for structured families.
|
|
@@ -67,11 +67,32 @@ So a triangular integral should be written as
|
|
|
67
67
|
multiple_integrate(1, (y, 0, 1 - x), (x, 0, 1))
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
not with the structural outer-to-inner ordering used, for example, by Mathematica. The same convention is used by
|
|
71
|
-
`region_from_ranges(...)` when regions are recognized from dependent bounds.
|
|
70
|
+
not with the structural outer-to-inner ordering used, for example, by Mathematica. The same convention is used by `multiple_integrate`; standalone `region_from_ranges(...)` can also inspect an explicitly requested structural order for geometric classification.
|
|
72
71
|
|
|
73
72
|
This matters especially for triangular, disk, and ball examples with dependent bounds: write the tuples in the same order you would pass them to `sympy.integrate`.
|
|
74
73
|
|
|
74
|
+
When bounds contain symbolic parameters, specialized geometric formulas are used only when their orientation can be established from the supplied assumptions. This preserves the signed semantics of iterated integration.
|
|
75
|
+
|
|
76
|
+
## Assumptions
|
|
77
|
+
|
|
78
|
+
Parameter assumptions can be supplied with ordinary SymPy relations or ``Q`` predicates:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import sympy as sp
|
|
82
|
+
from multiple_integrate import multiple_integrate
|
|
83
|
+
|
|
84
|
+
x, a = sp.symbols("x a", real=True)
|
|
85
|
+
|
|
86
|
+
multiple_integrate(
|
|
87
|
+
sp.exp(-a*x),
|
|
88
|
+
(x, 0, sp.oo),
|
|
89
|
+
assumptions={a > 0},
|
|
90
|
+
)
|
|
91
|
+
# 1/a
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A single condition such as ``assumptions=a > 0`` is also accepted, as are lists, tuples, sets, and SymPy predicates such as ``Q.positive(a)``. Contradictory assumptions raise ``ValueError``.
|
|
95
|
+
|
|
75
96
|
---
|
|
76
97
|
|
|
77
98
|
## Representative multiple-integral examples
|
|
@@ -217,21 +238,25 @@ Here the inner function is `g(x, y) = x + y`. Its level sets in the first quadra
|
|
|
217
238
|
|
|
218
239
|
## Region model
|
|
219
240
|
|
|
220
|
-
The solver
|
|
241
|
+
The solver normalizes input bounds into explicit region objects before applying structural shortcuts.
|
|
221
242
|
|
|
222
|
-
|
|
243
|
+
Region support includes:
|
|
223
244
|
|
|
224
245
|
- `BoxRegion` for product domains with independent bounds
|
|
225
246
|
- `IteratedRegion` for general nested bounds
|
|
226
247
|
- `SimplexRegion` for standard simplex-style regions
|
|
227
248
|
- `AffineSimplexRegion` for affine images of standard simplices
|
|
228
249
|
- `GraphRegion` for simple affine graph-bounded 2D regions
|
|
229
|
-
- `DiskRegion` for
|
|
230
|
-
- `BallRegion` for
|
|
231
|
-
- `EllipsoidRegion` for axis-aligned
|
|
250
|
+
- `DiskRegion` for centered or translated axis-aligned disks
|
|
251
|
+
- `BallRegion` for centered or translated axis-aligned balls
|
|
252
|
+
- `EllipsoidRegion` for centered or translated axis-aligned ellipsoids
|
|
232
253
|
- `AnnulusRegion` for centered annuli
|
|
233
254
|
- `SphericalShellRegion` for centered spherical shells
|
|
234
|
-
- `UnionRegion` for finite unions of supported regions
|
|
255
|
+
- `UnionRegion` for finite disjoint unions of supported regions with matching ordered variables
|
|
256
|
+
|
|
257
|
+
Region constructors validate structural invariants such as distinct SymPy variables, compatible dimensions, nonnegative radii, positive ellipsoid semi-axes, and consistent nested dependencies. `UnionRegion` has additive **disjoint-union** semantics: pieces must share the same ordered variables and be disjoint up to measure-zero boundaries. Obvious overlaps among supported concentric radial pieces are rejected; if symbolic overlap cannot be decided, callers remain responsible for the disjointness claim.
|
|
258
|
+
|
|
259
|
+
Translated disks, balls, and axis-aligned ellipsoids are recognized directly from shifted Cartesian bounds. Their `center` coordinates are stored in the same order as `region.variables`, and polynomial/radial shortcuts operate relative to that center.
|
|
235
260
|
|
|
236
261
|
This improves:
|
|
237
262
|
|
|
@@ -277,7 +302,7 @@ However, it is still **not** a full symbolic region engine. In particular, it do
|
|
|
277
302
|
|
|
278
303
|
- general geometric region rewriting
|
|
279
304
|
- automatic order reversal for arbitrary dependent bounds
|
|
280
|
-
- a full region algebra
|
|
305
|
+
- a full region algebra
|
|
281
306
|
- unrestricted automatic polar/spherical coordinate changes
|
|
282
307
|
- arbitrary semialgebraic cell decomposition
|
|
283
308
|
|
|
@@ -332,6 +357,6 @@ GPL-3.0-or-later
|
|
|
332
357
|
|
|
333
358
|
---
|
|
334
359
|
|
|
335
|
-
##
|
|
360
|
+
## Limitations
|
|
336
361
|
|
|
337
|
-
|
|
362
|
+
The package includes exact simplex / Dirichlet formulas and coordinate changes for selected disks, balls, shells, and ellipsoids, but it does **not** attempt completely general geometric rewriting or arbitrary symbolic substitutions. It is best viewed as a recognition-driven exact integrator for structured families.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Core integration engine
|
|
2
|
+
=======================
|
|
3
|
+
|
|
4
|
+
The supported top-level entry point is
|
|
5
|
+
:func:`multiple_integrate.multiple_integrate`. This page also exposes
|
|
6
|
+
implementation data structures documented in ``core.py`` for contributors and
|
|
7
|
+
advanced users.
|
|
8
|
+
|
|
9
|
+
.. automodule:: multiple_integrate.core
|
|
10
|
+
:members:
|
|
11
|
+
:show-inheritance:
|
|
12
|
+
:member-order: bysource
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
API reference
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
This reference is generated from docstrings in ``src/multiple_integrate``.
|
|
5
|
+
|
|
6
|
+
The package-root page documents the supported public surface. The module pages
|
|
7
|
+
provide fuller technical detail for region objects, classification helpers, and
|
|
8
|
+
core integration data structures.
|
|
9
|
+
|
|
10
|
+
.. toctree::
|
|
11
|
+
:maxdepth: 2
|
|
12
|
+
|
|
13
|
+
public
|
|
14
|
+
regions
|
|
15
|
+
core
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# API Reference
|
|
2
2
|
|
|
3
|
-
This page documents the public API and the main internal data structures used by
|
|
3
|
+
This page documents the public API and the main internal data structures used by `src/multiple_integrate/`.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -13,7 +13,6 @@ def multiple_integrate(
|
|
|
13
13
|
f: sympy.Expr,
|
|
14
14
|
*ranges,
|
|
15
15
|
assumptions=None,
|
|
16
|
-
generate_conditions: bool = False,
|
|
17
16
|
principal_value: bool = False,
|
|
18
17
|
) -> sympy.Expr
|
|
19
18
|
```
|
|
@@ -26,9 +25,8 @@ Symbolically evaluate a definite or multiple integral.
|
|
|
26
25
|
|---|---|---|
|
|
27
26
|
| `f` | `sympy.Expr` | Integrand as a SymPy expression. |
|
|
28
27
|
| `*ranges` | `tuple(symbol, lower, upper)` | Integration ranges in **inner-first iterated order**, matching `sympy.integrate`. |
|
|
29
|
-
| `assumptions` |
|
|
30
|
-
| `
|
|
31
|
-
| `principal_value` | `bool` | Request Cauchy principal value in supported fallback calls. |
|
|
28
|
+
| `assumptions` | SymPy Boolean condition or iterable, optional | Mathematical assumptions such as `a > 0`, `{a > 0, b != 0}`, or `{Q.positive(a)}`. Conditions are normalized to SymPy predicates and used by convergence checks and symbolic refinement. |
|
|
29
|
+
| `principal_value` | `bool` | Request a one-dimensional Cauchy principal value. Multidimensional principal values are currently rejected explicitly. |
|
|
32
30
|
|
|
33
31
|
**Returns**
|
|
34
32
|
|
|
@@ -38,7 +36,8 @@ A `sympy.Expr`. On some unsupported inputs the result may remain an unevaluated
|
|
|
38
36
|
|
|
39
37
|
- The first range tuple is the **innermost** integral.
|
|
40
38
|
- The solver may use region-aware exact formulas, coordinate changes, symmetry reductions, decomposition-based methods, or raw SymPy fallback.
|
|
41
|
-
-
|
|
39
|
+
- Symmetry shortcuts are disabled when an interior singularity may make ordinary and principal-value integration differ.
|
|
40
|
+
- The package still does **not** attempt complete general convergence analysis for arbitrary multidimensional integrals.
|
|
42
41
|
|
|
43
42
|
**Example**
|
|
44
43
|
|
|
@@ -109,26 +108,12 @@ These live in `src/multiple_integrate/regions.py`.
|
|
|
109
108
|
|
|
110
109
|
---
|
|
111
110
|
|
|
112
|
-
##
|
|
111
|
+
## Internal decomposition support
|
|
113
112
|
|
|
114
|
-
|
|
113
|
+
The composition-analysis helper is internal. ``_IntegrandDecomposition`` stores
|
|
114
|
+
``f_outer``, ``g_inner``, and ``is_polynomial`` for strategy dispatch, but it is
|
|
115
|
+
not exported from ``multiple_integrate`` and is not part of the public API.
|
|
115
116
|
|
|
116
|
-
```python
|
|
117
|
-
class Decomposition:
|
|
118
|
-
f_outer: Callable
|
|
119
|
-
g_inner: sympy.Expr
|
|
120
|
-
is_polynomial: bool
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
A lightweight container describing an integrand decomposition of the form
|
|
124
|
-
|
|
125
|
-
\[
|
|
126
|
-
F(x_1,\dots,x_n) = f(g(x_1,\dots,x_n)).
|
|
127
|
-
\]
|
|
128
|
-
|
|
129
|
-
This still matters internally for several non-region-specific heuristics, but it is no longer the only organizing idea in the solver.
|
|
130
|
-
|
|
131
|
-
---
|
|
132
117
|
|
|
133
118
|
## Coordinate transforms
|
|
134
119
|
|
|
@@ -164,10 +149,10 @@ This is currently used for selected coordinate changes such as:
|
|
|
164
149
|
Contains:
|
|
165
150
|
|
|
166
151
|
- `multiple_integrate`
|
|
167
|
-
- `
|
|
152
|
+
- `_IntegrandDecomposition` (internal)
|
|
168
153
|
- `CoordinateTransform`
|
|
169
154
|
- decomposition helpers
|
|
170
|
-
-
|
|
155
|
+
- method selection and iterated fallback
|
|
171
156
|
- selected exact family solvers
|
|
172
157
|
|
|
173
158
|
### `multiple_integrate.regions`
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Nested ranges and region classification
|
|
2
|
+
=======================================
|
|
3
|
+
|
|
4
|
+
The range convention
|
|
5
|
+
--------------------
|
|
6
|
+
|
|
7
|
+
MultipleIntegrate uses the same iterated-integral ordering as
|
|
8
|
+
``sympy.integrate``: the **first range is integrated first**, so it is the
|
|
9
|
+
innermost integral.
|
|
10
|
+
|
|
11
|
+
For example,
|
|
12
|
+
|
|
13
|
+
.. math::
|
|
14
|
+
|
|
15
|
+
\int_0^1 \int_0^{1-x} f(x,y)\,dy\,dx
|
|
16
|
+
|
|
17
|
+
is written as:
|
|
18
|
+
|
|
19
|
+
.. code-block:: python
|
|
20
|
+
|
|
21
|
+
multiple_integrate(
|
|
22
|
+
f,
|
|
23
|
+
(y, 0, 1 - x),
|
|
24
|
+
(x, 0, 1),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
The upper bound of ``y`` may depend on ``x`` because ``x`` remains free while
|
|
28
|
+
the inner ``y`` integral is evaluated. Reversing those tuples without deriving
|
|
29
|
+
new bounds generally describes a different iterated integral.
|
|
30
|
+
|
|
31
|
+
Ranges as geometry
|
|
32
|
+
------------------
|
|
33
|
+
|
|
34
|
+
A list of nested ranges describes more than an evaluation order: it also
|
|
35
|
+
describes a geometric domain. ``region_from_ranges`` analyzes that structure
|
|
36
|
+
and returns the most specific region type it can justify.
|
|
37
|
+
|
|
38
|
+
Conceptually, classification proceeds from informative geometric patterns
|
|
39
|
+
toward a generic fallback:
|
|
40
|
+
|
|
41
|
+
.. code-block:: text
|
|
42
|
+
|
|
43
|
+
nested ranges
|
|
44
|
+
|
|
|
45
|
+
+-- independent constant bounds --------> BoxRegion
|
|
46
|
+
|
|
|
47
|
+
+-- standard simplex bounds ------------> SimplexRegion
|
|
48
|
+
|
|
|
49
|
+
+-- affine simplex bounds --------------> AffineSimplexRegion
|
|
50
|
+
|
|
|
51
|
+
+-- circular / spherical bounds --------> DiskRegion / BallRegion
|
|
52
|
+
|
|
|
53
|
+
+-- unequal quadratic semi-axes --------> EllipsoidRegion
|
|
54
|
+
|
|
|
55
|
+
+-- dependent graph bounds -------------> GraphRegion
|
|
56
|
+
|
|
|
57
|
+
`-- otherwise --------------------------> IteratedRegion
|
|
58
|
+
|
|
59
|
+
Classification is deliberately conservative. A specialized region should be
|
|
60
|
+
returned only when the bounds establish the corresponding geometry. This
|
|
61
|
+
matters because specialized objects can provide exact volume, moment, radial,
|
|
62
|
+
symmetry, or change-of-variables shortcuts.
|
|
63
|
+
|
|
64
|
+
Structural orientation versus public ordering
|
|
65
|
+
----------------------------------------------
|
|
66
|
+
|
|
67
|
+
Geometric recognition is often easiest when bounds are inspected
|
|
68
|
+
outer-to-inner, whereas the public API follows SymPy's inner-first convention.
|
|
69
|
+
The classifier may therefore inspect plausible structural orientations
|
|
70
|
+
internally. Once a region is recognized, its stored ranges are canonicalized
|
|
71
|
+
back to the package's public inner-first convention.
|
|
72
|
+
|
|
73
|
+
Users should continue to supply ranges exactly as they would to
|
|
74
|
+
``sympy.integrate``.
|
|
75
|
+
|
|
76
|
+
Why classification matters
|
|
77
|
+
--------------------------
|
|
78
|
+
|
|
79
|
+
Region recognition can replace a difficult symbolic iterated integral with an
|
|
80
|
+
exact formula. Specialized paths include:
|
|
81
|
+
|
|
82
|
+
* constant volume for boxes, disks, balls, ellipsoids, annuli, spherical
|
|
83
|
+
shells, and simplices;
|
|
84
|
+
* polynomial moments on several structured regions;
|
|
85
|
+
* cancellation of odd terms on reflection-invariant domains;
|
|
86
|
+
* polar or spherical changes of variables for suitable radial integrands;
|
|
87
|
+
* affine normalization of an affine simplex to a standard simplex;
|
|
88
|
+
* selected graph-region order reversal.
|
|
89
|
+
|
|
90
|
+
If no specialized path applies, ``multiple_integrate`` retains a general
|
|
91
|
+
iterated SymPy integration fallback. Region recognition is therefore an
|
|
92
|
+
optimization and reasoning layer rather than a requirement for every valid
|
|
93
|
+
integral.
|
|
94
|
+
|
|
95
|
+
Inspecting a classification
|
|
96
|
+
---------------------------
|
|
97
|
+
|
|
98
|
+
.. code-block:: python
|
|
99
|
+
|
|
100
|
+
import sympy as sp
|
|
101
|
+
from multiple_integrate import region_from_ranges
|
|
102
|
+
|
|
103
|
+
x, y = sp.symbols("x y", real=True)
|
|
104
|
+
|
|
105
|
+
ranges = [
|
|
106
|
+
(y, 0, 1 - x),
|
|
107
|
+
(x, 0, 1),
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
region = region_from_ranges(ranges)
|
|
111
|
+
|
|
112
|
+
print(type(region).__name__)
|
|
113
|
+
print(region.variables)
|
|
114
|
+
print(region.normalized_ranges())
|
|
115
|
+
print(region.constant_volume())
|
|
116
|
+
|
|
117
|
+
The region classes and classification functions are documented in
|
|
118
|
+
:doc:`../api/regions`.
|
|
119
|
+
|
|
120
|
+
Translated quadrics
|
|
121
|
+
-------------------
|
|
122
|
+
|
|
123
|
+
Disk, ball, and axis-aligned ellipsoid recognition also accepts coordinate shifts.
|
|
124
|
+
For example, bounds equivalent to ``(x-cx)**2 + (y-cy)**2 <= R**2`` produce a
|
|
125
|
+
``DiskRegion`` with a nonzero ``center``. The center tuple follows the same variable
|
|
126
|
+
ordering as ``region.variables``. Polynomial moments and radial shortcuts translate to
|
|
127
|
+
the stored center before applying the standard centered formulas.
|
|
128
|
+
|
|
129
|
+
Union semantics
|
|
130
|
+
---------------
|
|
131
|
+
|
|
132
|
+
``UnionRegion`` represents an additive disjoint decomposition, not a general
|
|
133
|
+
inclusion-exclusion set union. Pieces must use the same ordered variables and be
|
|
134
|
+
disjoint up to measure-zero boundaries. The constructor rejects overlaps it can prove
|
|
135
|
+
for supported concentric radial pieces; when symbolic overlap is undecidable, the
|
|
136
|
+
caller is responsible for supplying disjoint pieces.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Sphinx configuration for the MultipleIntegrate documentation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
project = "MultipleIntegrate"
|
|
6
|
+
author = "Bhuvanesh Bhatt"
|
|
7
|
+
copyright = "2026, Bhuvanesh Bhatt"
|
|
8
|
+
|
|
9
|
+
extensions = [
|
|
10
|
+
"sphinx.ext.autodoc",
|
|
11
|
+
"sphinx.ext.autosummary",
|
|
12
|
+
"sphinx.ext.intersphinx",
|
|
13
|
+
"sphinx.ext.mathjax",
|
|
14
|
+
"sphinx.ext.napoleon",
|
|
15
|
+
"sphinx.ext.viewcode",
|
|
16
|
+
"myst_parser",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
autosummary_generate = True
|
|
20
|
+
autodoc_member_order = "bysource"
|
|
21
|
+
autodoc_typehints = "description"
|
|
22
|
+
|
|
23
|
+
napoleon_google_docstring = True
|
|
24
|
+
napoleon_numpy_docstring = True
|
|
25
|
+
|
|
26
|
+
source_suffix = {
|
|
27
|
+
".rst": "restructuredtext",
|
|
28
|
+
".md": "markdown",
|
|
29
|
+
}
|
|
30
|
+
master_doc = "index"
|
|
31
|
+
|
|
32
|
+
exclude_patterns = [
|
|
33
|
+
"_build",
|
|
34
|
+
"Thumbs.db",
|
|
35
|
+
".DS_Store",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
html_theme = "sphinx_rtd_theme"
|
|
39
|
+
html_title = "MultipleIntegrate"
|
|
40
|
+
|
|
41
|
+
intersphinx_mapping = {
|
|
42
|
+
"python": ("https://docs.python.org/3/", None),
|
|
43
|
+
"sympy": ("https://docs.sympy.org/latest/", None),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
myst_enable_extensions = [
|
|
47
|
+
"dollarmath",
|
|
48
|
+
"colon_fence",
|
|
49
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Contributing
|
|
2
2
|
|
|
3
|
-
This guide explains the
|
|
3
|
+
This guide explains the project layout and the main ideas to keep in mind when extending MultipleIntegrate.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -28,9 +28,9 @@ docs/
|
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
-
##
|
|
31
|
+
## Architecture
|
|
32
32
|
|
|
33
|
-
The package
|
|
33
|
+
The package uses the following layered flow:
|
|
34
34
|
|
|
35
35
|
```text
|
|
36
36
|
multiple_integrate(expr, *ranges)
|
|
@@ -77,7 +77,7 @@ Typical tasks:
|
|
|
77
77
|
|
|
78
78
|
If a class of integrals has a stable closed form, prefer implementing that directly rather than relying on generic SymPy definite integration.
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Examples include:
|
|
81
81
|
|
|
82
82
|
- simplex / Dirichlet formulas
|
|
83
83
|
- selected disk / ball / shell formulas
|
|
@@ -95,13 +95,13 @@ When extending this area, focus on transformations that are:
|
|
|
95
95
|
|
|
96
96
|
### 4. Decomposition-based methods
|
|
97
97
|
|
|
98
|
-
`
|
|
98
|
+
`_IntegrandDecomposition` and its helpers still matter for monotone, piecewise-monotone, separable, and selected layer-cake style methods.
|
|
99
99
|
|
|
100
|
-
This is a useful extension area, but it
|
|
100
|
+
This is a useful extension area, but it is best treated as one family inside the broader solver.
|
|
101
101
|
|
|
102
102
|
---
|
|
103
103
|
|
|
104
|
-
## Adding a
|
|
104
|
+
## Adding a capability
|
|
105
105
|
|
|
106
106
|
A good pattern is:
|
|
107
107
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Integrand composition analysis
|
|
2
|
+
|
|
3
|
+
MultipleIntegrate analyzes some integrands as a composition
|
|
4
|
+
|
|
5
|
+
$$
|
|
6
|
+
F(x_1,\ldots,x_n)=f(g(x_1,\ldots,x_n)).
|
|
7
|
+
$$
|
|
8
|
+
|
|
9
|
+
The private `_IntegrandDecomposition` container stores the outer callable `f_outer`, the inner expression `g_inner`, and whether `g_inner` is polynomial in the integration variables. This representation is used only by the planner.
|
|
10
|
+
|
|
11
|
+
## Recognized structures
|
|
12
|
+
|
|
13
|
+
### Polynomial inner expressions
|
|
14
|
+
|
|
15
|
+
A polynomial integrand can use the identity outer function, with the polynomial itself as `g_inner`. This representation enables linear pushforward, quadratic full-space, simplex, and polynomial level-set formulas.
|
|
16
|
+
|
|
17
|
+
### Single-argument functions
|
|
18
|
+
|
|
19
|
+
Expressions such as `exp(g)`, `sin(g)`, `cos(g)`, and `log(g)` are represented by replacing the function argument with a fresh dummy variable. For example, `exp(x**2 + y)` becomes an outer function `exp(t)` and inner expression `x**2 + y`.
|
|
20
|
+
|
|
21
|
+
### Powers with integration-independent exponents
|
|
22
|
+
|
|
23
|
+
An expression such as `(x**2 + 1)**(3/2)` is represented by the outer function `t**(3/2)` and inner expression `x**2 + 1`.
|
|
24
|
+
|
|
25
|
+
### Constant factor and addend peeling
|
|
26
|
+
|
|
27
|
+
Integration-independent factors and addends are separated before recursively analyzing the variable-dependent expression. For example, `3*sin(x)` can be represented with inner expression `x` and outer function `3*sin(t)`.
|
|
28
|
+
|
|
29
|
+
### Single active variables
|
|
30
|
+
|
|
31
|
+
Any expression depending on exactly one integration variable can use the identity outer function. This supports monotone and piecewise-monotone change-of-variable methods for expressions such as `tan(x)`, `1/x`, and `x*log(x)`.
|
|
32
|
+
|
|
33
|
+
## When composition analysis declines
|
|
34
|
+
|
|
35
|
+
A multivariable expression that has no supported compositional structure is left to separability checks, region-specific formulas, or ordinary iterated integration. Declining a decomposition is intentional: the planner should not force an artificial `f(g)` representation when doing so would obscure variable dependence or make a structural method unsound.
|
|
36
|
+
|
|
37
|
+
## Polynomial flag
|
|
38
|
+
|
|
39
|
+
The `is_polynomial` flag determines whether polynomial-only formulas are eligible. Non-polynomial inner expressions skip coefficient extraction and polynomial geometry tests, then proceed directly to methods that support general symbolic inner functions.
|