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.
Files changed (67) hide show
  1. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/PKG-INFO +46 -20
  2. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/README.md +36 -11
  3. multiple_integrate-2.1.0/docs/Makefile +12 -0
  4. multiple_integrate-2.1.0/docs/api/core.rst +12 -0
  5. multiple_integrate-2.1.0/docs/api/index.rst +15 -0
  6. multiple_integrate-2.1.0/docs/api/public.rst +8 -0
  7. multiple_integrate-2.1.0/docs/api/regions.rst +7 -0
  8. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/docs/api.md +11 -26
  9. multiple_integrate-2.1.0/docs/concepts/ranges-and-regions.rst +136 -0
  10. multiple_integrate-2.1.0/docs/conf.py +49 -0
  11. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/docs/contributing.md +7 -7
  12. multiple_integrate-2.1.0/docs/decomposition.md +39 -0
  13. multiple_integrate-2.1.0/docs/examples/regions.rst +204 -0
  14. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/docs/examples.md +10 -10
  15. multiple_integrate-2.1.0/docs/index.rst +53 -0
  16. multiple_integrate-2.1.0/docs/installation.rst +43 -0
  17. multiple_integrate-2.1.0/docs/quickstart.rst +89 -0
  18. multiple_integrate-2.1.0/docs/strategies.md +37 -0
  19. multiple_integrate-2.1.0/docs/testing.md +33 -0
  20. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/docs/theory.md +19 -8
  21. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/notebooks/multiple_integration.ipynb +1066 -245
  22. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/pyproject.toml +12 -13
  23. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/src/multiple_integrate/__init__.py +2 -2
  24. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/src/multiple_integrate/core.py +857 -738
  25. multiple_integrate-2.1.0/src/multiple_integrate/regions.py +1588 -0
  26. multiple_integrate-2.1.0/tests/test_assumptions_api.py +87 -0
  27. multiple_integrate-2.1.0/tests/test_dispatch_safety.py +141 -0
  28. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_multiple_integrate.py +116 -112
  29. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_reference_examples.py +1 -1
  30. multiple_integrate-2.0.0/tests/test_regions_misclassification.py → multiple_integrate-2.1.0/tests/test_region_classification_boundaries.py +4 -2
  31. multiple_integrate-2.1.0/tests/test_region_invariants_and_matrix.py +193 -0
  32. multiple_integrate-2.1.0/tests/test_symbolic_conditional_convergence.py +47 -0
  33. multiple_integrate-2.1.0/tests/test_translated_regions.py +89 -0
  34. multiple_integrate-2.1.0/tests/test_validation_and_timeouts.py +140 -0
  35. multiple_integrate-2.0.0/docs/changelog.md +0 -125
  36. multiple_integrate-2.0.0/docs/decomposition.md +0 -239
  37. multiple_integrate-2.0.0/docs/index.md +0 -163
  38. multiple_integrate-2.0.0/docs/strategies.md +0 -138
  39. multiple_integrate-2.0.0/docs/testing.md +0 -123
  40. multiple_integrate-2.0.0/src/multiple_integrate/regions.py +0 -1125
  41. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/.gitignore +0 -0
  42. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/LICENSE +0 -0
  43. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/__init__.py +0 -0
  44. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/helpers.py +0 -0
  45. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_boole_piecewise.py +0 -0
  46. /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
  47. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_coordinate_changes.py +0 -0
  48. /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
  49. /multiple_integrate-2.0.0/tests/test_performance_regressions.py → /multiple_integrate-2.1.0/tests/test_performance_guards.py +0 -0
  50. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_reference_examples_notebook.py +0 -0
  51. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_region_order_sympy_convention.py +0 -0
  52. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_advanced_shapes.py +0 -0
  53. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_boxes_and_simplex.py +0 -0
  54. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_classification_priority.py +0 -0
  55. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_disk_and_ball.py +0 -0
  56. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_graph_and_simplex.py +0 -0
  57. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_graph_reversal.py +0 -0
  58. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_interactions.py +0 -0
  59. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_moments_box_simplex.py +0 -0
  60. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_moments_disk_ball.py +0 -0
  61. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_parse.py +0 -0
  62. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_radial.py +0 -0
  63. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_regions_symmetry.py +0 -0
  64. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_simplex_dirichlet_engine.py +0 -0
  65. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_singular_and_divergent_cases.py +0 -0
  66. {multiple_integrate-2.0.0 → multiple_integrate-2.1.0}/tests/test_supported_families.py +0 -0
  67. /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.0.0
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://BhuvaneshBhatt.github.io/multiple_integrate
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.11
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: mkdocs-material>=9.5; extra == 'docs'
45
- Requires-Dist: mkdocs>=1.5; extra == 'docs'
46
- Requires-Dist: pymdown-extensions>=10.7; extra == 'docs'
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 now normalizes input bounds into explicit region objects before applying several structural shortcuts.
301
+ The solver normalizes input bounds into explicit region objects before applying structural shortcuts.
280
302
 
281
- Current region support includes:
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 standard centered disks
289
- - `BallRegion` for standard centered balls
290
- - `EllipsoidRegion` for axis-aligned centered ellipsoids
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 comparable to symbolic `Region` objects
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
- ## Current limitations
420
+ ## Limitations
395
421
 
396
- Recent additions include exact simplex / Dirichlet formulas and the coordinate-change layer for selected disks, balls, shells, and ellipsoids, butthe package still does **not** attempt completely general geometric rewriting or arbitrary symbolic substitutions. It is best viewed as a recognition-driven exact integrator for structured families.
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 now normalizes input bounds into explicit region objects before applying several structural shortcuts.
241
+ The solver normalizes input bounds into explicit region objects before applying structural shortcuts.
221
242
 
222
- Current region support includes:
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 standard centered disks
230
- - `BallRegion` for standard centered balls
231
- - `EllipsoidRegion` for axis-aligned centered ellipsoids
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 comparable to symbolic `Region` objects
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
- ## Current limitations
360
+ ## Limitations
336
361
 
337
- Recent additions include exact simplex / Dirichlet formulas and the coordinate-change layer for selected disks, balls, shells, and ellipsoids, butthe package still does **not** attempt completely general geometric rewriting or arbitrary symbolic substitutions. It is best viewed as a recognition-driven exact integrator for structured families.
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
+ SPHINXOPTS ?= -W
2
+ SPHINXBUILD ?= sphinx-build
3
+ SOURCEDIR = .
4
+ BUILDDIR = _build
5
+
6
+ .PHONY: html clean
7
+
8
+ html:
9
+ $(SPHINXBUILD) $(SPHINXOPTS) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html"
10
+
11
+ clean:
12
+ rm -rf "$(BUILDDIR)"
@@ -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
@@ -0,0 +1,8 @@
1
+ Public package API
2
+ ==================
3
+
4
+ .. automodule:: multiple_integrate
5
+ :members:
6
+ :undoc-members:
7
+ :show-inheritance:
8
+ :member-order: bysource
@@ -0,0 +1,7 @@
1
+ Regions and classification
2
+ ==========================
3
+
4
+ .. automodule:: multiple_integrate.regions
5
+ :members:
6
+ :show-inheritance:
7
+ :member-order: bysource
@@ -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 the current `src/multiple_integrate/` package.
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` | `dict`, optional | Extra assumptions forwarded to selected SymPy calls. |
30
- | `generate_conditions` | `bool` | Request conditional output from SymPy in supported fallback calls. |
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
- - The package performs some structured-path safety checks, but it does **not** attempt complete general convergence analysis for arbitrary integrals.
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
- ## Decomposition support
111
+ ## Internal decomposition support
113
112
 
114
- ### `Decomposition`
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
- - `Decomposition`
152
+ - `_IntegrandDecomposition` (internal)
168
153
  - `CoordinateTransform`
169
154
  - decomposition helpers
170
- - planner / fallback logic
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 current project layout and the main ideas to keep in mind when extending MultipleIntegrate.
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
- ## Current architectural picture
31
+ ## Architecture
32
32
 
33
- The package is no longer just a single decomposition-first engine. The current flow is closer to:
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
- Current examples include:
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
- `Decomposition` and its helpers still matter for monotone, piecewise-monotone, separable, and selected layer-cake style methods.
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 should now be thought of as one family inside the broader solver.
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 new capability
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.