warpSPHBoundaries 0.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 (105) hide show
  1. warpsphboundaries-0.1.0/LICENSE +21 -0
  2. warpsphboundaries-0.1.0/MANIFEST.in +3 -0
  3. warpsphboundaries-0.1.0/PKG-INFO +126 -0
  4. warpsphboundaries-0.1.0/README.md +92 -0
  5. warpsphboundaries-0.1.0/pyproject.toml +44 -0
  6. warpsphboundaries-0.1.0/setup.cfg +4 -0
  7. warpsphboundaries-0.1.0/src/warpSPHBoundaries/__init__.py +18 -0
  8. warpsphboundaries-0.1.0/src/warpSPHBoundaries/curvbound/__init__.py +24 -0
  9. warpsphboundaries-0.1.0/src/warpSPHBoundaries/curvbound/kernels.py +57 -0
  10. warpsphboundaries-0.1.0/src/warpSPHBoundaries/curvbound/oracle.py +133 -0
  11. warpsphboundaries-0.1.0/src/warpSPHBoundaries/curvbound/symbolic.py +178 -0
  12. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/symbolic/planar_export.txt +19 -0
  13. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/symbolic/sphere_export.txt +13 -0
  14. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/disk_cone_3_3_12_12_6_3_12_12.npz +0 -0
  15. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/disk_lw2_3_3_12_12_6_3_12_12.npz +0 -0
  16. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/disk_lw4_3_3_12_12_6_3_12_12.npz +0 -0
  17. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/disk_w2_3_3_12_12_6_3_12_12.npz +0 -0
  18. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/disk_w2p5_3_3_12_12_6_3_12_12.npz +0 -0
  19. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/disk_w4_3_3_12_12_6_3_12_12.npz +0 -0
  20. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/disk_w4p5_3_3_12_12_6_3_12_12.npz +0 -0
  21. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/tier3_b7.npz +0 -0
  22. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/tier3_b8.npz +0 -0
  23. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/tier3_cubic.npz +0 -0
  24. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/tier3_poly6.npz +0 -0
  25. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/tier3_quartic.npz +0 -0
  26. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/tier3_quintic.npz +0 -0
  27. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/tier3_w2.npz +0 -0
  28. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/tier3_w4.npz +0 -0
  29. warpsphboundaries-0.1.0/src/warpSPHBoundaries/data/tables/tier3_w6.npz +0 -0
  30. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/__init__.py +2 -0
  31. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/core.py +257 -0
  32. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/disk.py +92 -0
  33. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/disktables.py +127 -0
  34. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/fem.py +222 -0
  35. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/fem_fixtures.py +93 -0
  36. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/fem_study.py +66 -0
  37. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/fixtures.py +287 -0
  38. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/geometry.py +177 -0
  39. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/kernels.py +243 -0
  40. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/mpq.py +40 -0
  41. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/np2d.py +680 -0
  42. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/np_fem.py +225 -0
  43. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/oracle.py +256 -0
  44. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/precision.py +68 -0
  45. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/primitives.py +94 -0
  46. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/tier3.py +131 -0
  47. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/tier4.py +160 -0
  48. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/tier_select.py +44 -0
  49. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/torch2d.py +321 -0
  50. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/warp2d.py +502 -0
  51. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/warpbc.py +953 -0
  52. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/warpdisk.py +199 -0
  53. warpsphboundaries-0.1.0/src/warpSPHBoundaries/edge/warpfused.py +210 -0
  54. warpsphboundaries-0.1.0/src/warpSPHBoundaries/paths.py +50 -0
  55. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/__init__.py +20 -0
  56. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/boundaryOps.py +403 -0
  57. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/box.py +277 -0
  58. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/cone_area.py +155 -0
  59. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/cover.py +147 -0
  60. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/fixedadj.py +250 -0
  61. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/fused.py +751 -0
  62. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/implicitBodies.py +315 -0
  63. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/massprops.py +70 -0
  64. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/particles.py +195 -0
  65. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/periodic.py +41 -0
  66. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/provider.py +113 -0
  67. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/scene.py +1565 -0
  68. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/tensile.py +89 -0
  69. warpsphboundaries-0.1.0/src/warpSPHBoundaries/scene/viscosity.py +109 -0
  70. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/__init__.py +2 -0
  71. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/bcclosures.py +40 -0
  72. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/cases.py +136 -0
  73. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/deltasph2d.py +1031 -0
  74. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/dfsph2d.py +1054 -0
  75. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/dfsph_cases.py +36 -0
  76. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/dfsph_kernels.py +124 -0
  77. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/dfsph_ref.py +76 -0
  78. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/fluidwarp.py +145 -0
  79. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/graphstep.py +159 -0
  80. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/loads.py +73 -0
  81. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/modules/__init__.py +6 -0
  82. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/modules/shifting/__init__.py +5 -0
  83. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/modules/shifting/wp_deltaShiftRaw.py +285 -0
  84. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/modules/shifting/wp_minNeighbourNormalDot.py +229 -0
  85. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/modules/surfaceDetection/__init__.py +5 -0
  86. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/modules/surfaceDetection/wp_barecascoCone.py +265 -0
  87. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/modules/surfaceDetection/wp_barecascoCover.py +244 -0
  88. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/modules/wallComplement/__init__.py +5 -0
  89. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/modules/wallComplement/wp_complementFirstMoment.py +259 -0
  90. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/modules/wallComplement/wp_complementMoment.py +291 -0
  91. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/pairs.py +84 -0
  92. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/pinned.py +32 -0
  93. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/probes.py +64 -0
  94. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/projection.py +182 -0
  95. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/references.py +58 -0
  96. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/solvers.py +202 -0
  97. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/system.py +141 -0
  98. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/validation.py +195 -0
  99. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/wallclosure.py +251 -0
  100. warpsphboundaries-0.1.0/src/warpSPHBoundaries/sim/wallmoments.py +324 -0
  101. warpsphboundaries-0.1.0/src/warpSPHBoundaries.egg-info/PKG-INFO +126 -0
  102. warpsphboundaries-0.1.0/src/warpSPHBoundaries.egg-info/SOURCES.txt +103 -0
  103. warpsphboundaries-0.1.0/src/warpSPHBoundaries.egg-info/dependency_links.txt +1 -0
  104. warpsphboundaries-0.1.0/src/warpSPHBoundaries.egg-info/requires.txt +16 -0
  105. warpsphboundaries-0.1.0/src/warpSPHBoundaries.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rene Winchenbach
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ prune tests
2
+ prune docs
3
+ prune .tmp
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: warpSPHBoundaries
3
+ Version: 0.1.0
4
+ Summary: Exact SPH boundary integrals via divergence-theorem edge reductions (2D), scene layer and delta+-SPH / DFSPH solvers on analytic boundaries
5
+ License: MIT
6
+ Project-URL: Repository, https://github.com/wi-re/warpSPHBoundaries
7
+ Project-URL: Issues, https://github.com/wi-re/warpSPHBoundaries/issues
8
+ Keywords: sph,smoothed-particle-hydrodynamics,boundary-conditions,warp,pytorch,analytic-boundaries
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Scientific/Engineering
16
+ Classifier: Topic :: Scientific/Engineering :: Physics
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: numpy
21
+ Requires-Dist: scipy
22
+ Requires-Dist: mpmath
23
+ Requires-Dist: torch
24
+ Requires-Dist: warp-lang
25
+ Requires-Dist: warpSPHCore>=0.6.0
26
+ Provides-Extra: solvers
27
+ Requires-Dist: warpSPH>=0.6.0; extra == "solvers"
28
+ Requires-Dist: warpSPHCore>=0.6.0; extra == "solvers"
29
+ Provides-Extra: scripts
30
+ Requires-Dist: matplotlib; extra == "scripts"
31
+ Provides-Extra: test
32
+ Requires-Dist: pytest; extra == "test"
33
+ Dynamic: license-file
34
+
35
+ # curvatureBoundaries
36
+
37
+ Curvature-aware closed forms for the semi-analytic SPH boundary integral of
38
+ Winchenbach, Akhunov & Kolb (2020),
39
+
40
+ ```
41
+ lambda(x) = Int_{D_B(x)} W(|x-x'|, h) dx', W(r,h) = C_n/h^n W_hat(r/h)
42
+ ```
43
+
44
+ extended beyond the paper's 3-D planar cubic-spline result to:
45
+
46
+ * **2-D planar** boundaries for all four kernels — new arccos/ln closed forms
47
+ (`maple/01_planar.mpl`, §3 of `docs/derivation.md`);
48
+ * **3-D planar** Wendland kernels w2/w4/w6 — single polynomials of degree 2n
49
+ via the monomial basis `F(n,p,x)`;
50
+ * **curved boundaries**: solid sphere for the Wendland kernels — both
51
+ branches (`2R+d >= 1` and `2R+d < 1`) are rational `N(R,d)/(R+d)`, with
52
+ exact planar limit `R -> infinity`.
53
+
54
+ Kernels and conventions follow warpSPHCore
55
+ (`src/warpSPHCore/kernels/`, `scripts/kernels/kernel_specs.yaml`):
56
+ `q = r/h` with `h` the support radius.
57
+
58
+ ## Layout
59
+
60
+ ```
61
+ maple/00_setup.mpl kernel table, normalization, F(n,p,x) basis checks
62
+ maple/01_planar.mpl 2-D + 3-D planar, all kernels
63
+ maple/03_sphere.mpl solid sphere (Wendland w2/w4/w6)
64
+ maple/run_all.sh regenerate src/warpSPHBoundaries/data/symbolic/ (needs Maple 2026)
65
+ src/warpSPHBoundaries/curvbound/ PLAN track: kernel table, closed-form evaluators, oracles (curvature-aware planar / sphere integrals)
66
+ src/warpSPHBoundaries/edge/ HANDOFF track: exact 2D edge-reduction integrals (mpmath -> numpy -> torch -> Warp), FEM weights, tiers 3 / 4
67
+ src/warpSPHBoundaries/scene/ bodies and representations, boundary operations, wall operators of the delta+-SPH solver, the boundary provider (`AnalyticBoundary`, `scene/provider.py`)
68
+ src/warpSPHBoundaries/data/tables/ pre-tabulated tier-3 kernel tables (package data)
69
+ src/warpSPHBoundaries/sim/ DFSPH2D and DeltaSPH2D solvers, cases, validation runners
70
+ scripts/ command-line runners (regression harness, profiler, validation, videos), scripts/studies, scripts/bench
71
+ tests/ pytest suite: tests/{edge,scene,sim,curvbound} (940 tests)
72
+ notebooks/demo.ipynb executed demo: geometry, curves, validation
73
+ notebooks/examples/ nine example flows (Taylor-Green ... flow past a cylinder) on the delta+ and the DFSPH solver, against reference solutions
74
+ docs/derivation.md full mathematical write-up
75
+ PLAN.md project status & roadmap
76
+ src/warpSPHBoundaries/data/symbolic/ Maple exports (committed, shipped with the package; consumed by warpSPHBoundaries.curvbound)
77
+ results/figures/ notebook figures (generated, git-ignored)
78
+ ```
79
+
80
+ ## Package layering
81
+
82
+ `edge` <- `scene` <- `sim`: the edge machinery and the scene layer (`pip install .`, needs `warpSPHCore` only) are the boundary library `warpSPHBoundaries` (renamed from `edgebound` on 2026-10-06); `sim` (the reference
83
+ solvers) additionally needs `warpSPH`. A scheme consumes the analytic bodies through `warpSPHBoundaries.scene.AnalyticBoundary` (the `BoundaryProvider` protocol: integrals at the fluid particles, signed distance,
84
+ body kinematics); see docs/audit-warpsph-boundary-hooks.md. The layering is enforced by `tests/test_package_layering.py`.
85
+
86
+ ## Quick start
87
+
88
+ ```bash
89
+ # 1. (re)generate the symbolic results — needs Maple 2026
90
+ maple/run_all.sh # MAPLE=/path/to/maple to override
91
+
92
+ # 2. install (editable) and run the validation suite; the solvers need warpSPH / warpSPHCore in the same env
93
+ pip install -e . --no-deps # or: pip install -e .[test]
94
+ python -m pytest tests/ -q -n 4 # ~3 min with pytest-xdist; GPU tests use cuda:0
95
+
96
+ # 3. run the demo notebook (jupyter + matplotlib)
97
+ python -m jupyter nbconvert --to notebook --execute --inplace \
98
+ notebooks/demo.ipynb --ExecutePreprocessor.kernel_name=python3
99
+ ```
100
+
101
+ Python API sketch:
102
+
103
+ ```python
104
+ from fractions import Fraction
105
+ from curvbound import planar2d, planar3d, sphere
106
+
107
+ planar3d("w4", Fraction(1, 3)) # exact rational (polynomial in d)
108
+ planar2d("w4", "0.3") # mpf (arccos/ln closed form)
109
+ sphere("w4", Fraction(4, 10), Fraction(1, 5)) # solid ball R=0.4, d=0.2
110
+ ```
111
+
112
+ ## Validation
113
+
114
+ Every closed form is validated three ways (details in `docs/derivation.md`
115
+ §5): exact symbolic identities in Maple, 50-digit Maple quadrature, and
116
+ independent 40-digit mpmath oracles in `src/warpSPHBoundaries/curvbound/oracle.py`
117
+ (including a true 2-D Cartesian quadrature and the h-scaling identity).
118
+ Worst observed error: ~1e-39.
119
+
120
+ ## Status
121
+
122
+ 3-D planar (cubic + Wendland) and 3-D spherical (Wendland) were completed in
123
+ the original plan; the 2-D derivations, the promotion of the toy harnesses
124
+ to this repository layout, and the demo notebook are done (see `PLAN.md`
125
+ §18–§18c). Next: cylinder boundary, 2-D circle boundary, inside-solid
126
+ (`d < 0`) cases, gradient terms.
@@ -0,0 +1,92 @@
1
+ # curvatureBoundaries
2
+
3
+ Curvature-aware closed forms for the semi-analytic SPH boundary integral of
4
+ Winchenbach, Akhunov & Kolb (2020),
5
+
6
+ ```
7
+ lambda(x) = Int_{D_B(x)} W(|x-x'|, h) dx', W(r,h) = C_n/h^n W_hat(r/h)
8
+ ```
9
+
10
+ extended beyond the paper's 3-D planar cubic-spline result to:
11
+
12
+ * **2-D planar** boundaries for all four kernels — new arccos/ln closed forms
13
+ (`maple/01_planar.mpl`, §3 of `docs/derivation.md`);
14
+ * **3-D planar** Wendland kernels w2/w4/w6 — single polynomials of degree 2n
15
+ via the monomial basis `F(n,p,x)`;
16
+ * **curved boundaries**: solid sphere for the Wendland kernels — both
17
+ branches (`2R+d >= 1` and `2R+d < 1`) are rational `N(R,d)/(R+d)`, with
18
+ exact planar limit `R -> infinity`.
19
+
20
+ Kernels and conventions follow warpSPHCore
21
+ (`src/warpSPHCore/kernels/`, `scripts/kernels/kernel_specs.yaml`):
22
+ `q = r/h` with `h` the support radius.
23
+
24
+ ## Layout
25
+
26
+ ```
27
+ maple/00_setup.mpl kernel table, normalization, F(n,p,x) basis checks
28
+ maple/01_planar.mpl 2-D + 3-D planar, all kernels
29
+ maple/03_sphere.mpl solid sphere (Wendland w2/w4/w6)
30
+ maple/run_all.sh regenerate src/warpSPHBoundaries/data/symbolic/ (needs Maple 2026)
31
+ src/warpSPHBoundaries/curvbound/ PLAN track: kernel table, closed-form evaluators, oracles (curvature-aware planar / sphere integrals)
32
+ src/warpSPHBoundaries/edge/ HANDOFF track: exact 2D edge-reduction integrals (mpmath -> numpy -> torch -> Warp), FEM weights, tiers 3 / 4
33
+ src/warpSPHBoundaries/scene/ bodies and representations, boundary operations, wall operators of the delta+-SPH solver, the boundary provider (`AnalyticBoundary`, `scene/provider.py`)
34
+ src/warpSPHBoundaries/data/tables/ pre-tabulated tier-3 kernel tables (package data)
35
+ src/warpSPHBoundaries/sim/ DFSPH2D and DeltaSPH2D solvers, cases, validation runners
36
+ scripts/ command-line runners (regression harness, profiler, validation, videos), scripts/studies, scripts/bench
37
+ tests/ pytest suite: tests/{edge,scene,sim,curvbound} (940 tests)
38
+ notebooks/demo.ipynb executed demo: geometry, curves, validation
39
+ notebooks/examples/ nine example flows (Taylor-Green ... flow past a cylinder) on the delta+ and the DFSPH solver, against reference solutions
40
+ docs/derivation.md full mathematical write-up
41
+ PLAN.md project status & roadmap
42
+ src/warpSPHBoundaries/data/symbolic/ Maple exports (committed, shipped with the package; consumed by warpSPHBoundaries.curvbound)
43
+ results/figures/ notebook figures (generated, git-ignored)
44
+ ```
45
+
46
+ ## Package layering
47
+
48
+ `edge` <- `scene` <- `sim`: the edge machinery and the scene layer (`pip install .`, needs `warpSPHCore` only) are the boundary library `warpSPHBoundaries` (renamed from `edgebound` on 2026-10-06); `sim` (the reference
49
+ solvers) additionally needs `warpSPH`. A scheme consumes the analytic bodies through `warpSPHBoundaries.scene.AnalyticBoundary` (the `BoundaryProvider` protocol: integrals at the fluid particles, signed distance,
50
+ body kinematics); see docs/audit-warpsph-boundary-hooks.md. The layering is enforced by `tests/test_package_layering.py`.
51
+
52
+ ## Quick start
53
+
54
+ ```bash
55
+ # 1. (re)generate the symbolic results — needs Maple 2026
56
+ maple/run_all.sh # MAPLE=/path/to/maple to override
57
+
58
+ # 2. install (editable) and run the validation suite; the solvers need warpSPH / warpSPHCore in the same env
59
+ pip install -e . --no-deps # or: pip install -e .[test]
60
+ python -m pytest tests/ -q -n 4 # ~3 min with pytest-xdist; GPU tests use cuda:0
61
+
62
+ # 3. run the demo notebook (jupyter + matplotlib)
63
+ python -m jupyter nbconvert --to notebook --execute --inplace \
64
+ notebooks/demo.ipynb --ExecutePreprocessor.kernel_name=python3
65
+ ```
66
+
67
+ Python API sketch:
68
+
69
+ ```python
70
+ from fractions import Fraction
71
+ from curvbound import planar2d, planar3d, sphere
72
+
73
+ planar3d("w4", Fraction(1, 3)) # exact rational (polynomial in d)
74
+ planar2d("w4", "0.3") # mpf (arccos/ln closed form)
75
+ sphere("w4", Fraction(4, 10), Fraction(1, 5)) # solid ball R=0.4, d=0.2
76
+ ```
77
+
78
+ ## Validation
79
+
80
+ Every closed form is validated three ways (details in `docs/derivation.md`
81
+ §5): exact symbolic identities in Maple, 50-digit Maple quadrature, and
82
+ independent 40-digit mpmath oracles in `src/warpSPHBoundaries/curvbound/oracle.py`
83
+ (including a true 2-D Cartesian quadrature and the h-scaling identity).
84
+ Worst observed error: ~1e-39.
85
+
86
+ ## Status
87
+
88
+ 3-D planar (cubic + Wendland) and 3-D spherical (Wendland) were completed in
89
+ the original plan; the 2-D derivations, the promotion of the toy harnesses
90
+ to this repository layout, and the demo notebook are done (see `PLAN.md`
91
+ §18–§18c). Next: cylinder boundary, 2-D circle boundary, inside-solid
92
+ (`d < 0`) cases, gradient terms.
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "warpSPHBoundaries"
7
+ version = "0.1.0"
8
+ description = "Exact SPH boundary integrals via divergence-theorem edge reductions (2D), scene layer and delta+-SPH / DFSPH solvers on analytic boundaries"
9
+ readme = { file = "README.md", content-type = "text/markdown" }
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ keywords = ["sph", "smoothed-particle-hydrodynamics", "boundary-conditions", "warp", "pytorch", "analytic-boundaries"]
13
+ classifiers = [
14
+ "License :: OSI Approved :: MIT License",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Topic :: Scientific/Engineering",
21
+ "Topic :: Scientific/Engineering :: Physics",
22
+ ]
23
+ dependencies = ["numpy", "scipy", "mpmath", "torch", "warp-lang", "warpSPHCore>=0.6.0"]
24
+
25
+ [project.optional-dependencies]
26
+ # the solvers (`sim/`: delta+-SPH and DFSPH on analytic boundaries) run on warpSPH; warpSPH in turn uses this package for `--wallRepresentation analytic` (its `boundaries` extra)
27
+ solvers = ["warpSPH>=0.6.0", "warpSPHCore>=0.6.0"]
28
+ scripts = ["matplotlib"]
29
+ test = ["pytest"]
30
+
31
+ [project.urls]
32
+ Repository = "https://github.com/wi-re/warpSPHBoundaries"
33
+ Issues = "https://github.com/wi-re/warpSPHBoundaries/issues"
34
+
35
+ [tool.setuptools.packages.find]
36
+ where = ["src"]
37
+ include = ["warpSPHBoundaries*"]
38
+
39
+ [tool.setuptools.package-data]
40
+ warpSPHBoundaries = ["data/tables/*.npz", "data/symbolic/*.txt"]
41
+
42
+ [tool.pytest.ini_options]
43
+ testpaths = ["tests"]
44
+ markers = ["slow: builds / installs / long runs; deselect with -m \"not slow\""]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,18 @@
1
+ """warpSPHBoundaries -- exact 2D SPH boundary integrals via divergence-theorem edge reductions.
2
+
3
+ Sub-packages: `curvbound` (closed forms and quadrature oracles of the PLAN track), `edge` (the edge machinery), `scene` (bodies, boundary operations, wall operators), `sim` (DFSPH / delta+-SPH solvers); `paths`.
4
+ Derivations: docs/derivations/*.md. The 2D edge reductions (`edge`) and the PLAN-track closed forms (`curvbound`) are separate tracks that share the kernels.
5
+ """
6
+ __version__ = "0.1.0" # keep equal to pyproject.toml (scripts/publish_pypi.sh checks it)
7
+
8
+ from .edge.precision import ensure_default as _ensure_default # warpSPHCore precision: warpSPHBoundaries defaults to float64 unless configured (edge/precision.py); before any warpSPHCore import
9
+ _ensure_default()
10
+
11
+ from .edge.core import gradient, moment, moment_gradient, value
12
+ from .edge.geometry import prepare
13
+ from .edge.kernels import KERNELS, disk_moment, kernel
14
+ from .edge.oracle import polar_disk_gradient, polar_disk_moment, polar_disk_value, polar_gradient, polar_moment, polar_value
15
+
16
+ __all__ = ["value", "gradient", "moment", "moment_gradient", "prepare",
17
+ "KERNELS", "kernel", "disk_moment",
18
+ "polar_value", "polar_gradient", "polar_moment", "polar_disk_value", "polar_disk_moment", "polar_disk_gradient"]
@@ -0,0 +1,24 @@
1
+ """warpSPHBoundaries.curvbound — curvature-aware SPH boundary integrals (the PLAN track; formerly the top-level package `curvbound`).
2
+
3
+ Closed forms (Maple-generated, data/symbolic/) plus independent
4
+ high-precision quadrature oracles, for the semi-analytic SPH boundary
5
+ handling of Winchenbach, Akhunov, Kolb (2020) extended to Wendland kernels
6
+ and to curved (solid-sphere) boundaries.
7
+ """
8
+ from .kernels import KERNELS, Kernel, kernel
9
+ from .oracle import (
10
+ physical_planar3d,
11
+ quad_planar2d,
12
+ quad_planar2d_cartesian,
13
+ quad_planar3d,
14
+ quad_sphere,
15
+ )
16
+ from .symbolic import PLANAR_EXPORT, SPHERE_EXPORT, planar2d, planar3d, sphere
17
+
18
+ __all__ = [
19
+ "KERNELS", "Kernel", "kernel",
20
+ "PLANAR_EXPORT", "SPHERE_EXPORT",
21
+ "planar2d", "planar3d", "sphere",
22
+ "quad_planar2d", "quad_planar2d_cartesian", "quad_planar3d",
23
+ "quad_sphere", "physical_planar3d",
24
+ ]
@@ -0,0 +1,57 @@
1
+ """Kernel table for the boundary-integral derivations.
2
+
3
+ Conventions match warpSPHCore (src/warpSPHCore/kernels/kernel.py and
4
+ scripts/kernels/kernel_specs.yaml):
5
+
6
+ W(r, h) = C_n / h^n * W_hat(q), q = r / h, h = support radius,
7
+
8
+ with W_hat compactly supported on [0, 1]. The kernel shapes are identical
9
+ in 2D and 3D; only C_n differs (transcribed from kernel_specs.yaml):
10
+
11
+ cubic : W_hat = (1-q)^3 - 4*(1/2-q)_+^3 C2 = 80/(7*pi) C3 = 16/pi
12
+ w2 : (1-q)^4 (1+4q) C2 = 7/pi C3 = 21/(2*pi)
13
+ w4 : (1-q)^6 (1+6q+35/3*q^2) C2 = 9/pi C3 = 495/(32*pi)
14
+ w6 : (1-q)^8 (1+8q+25q^2+32q^3) C2 = 78/(7*pi) C3 = 1365/(64*pi)
15
+ """
16
+ from dataclasses import dataclass
17
+ from fractions import Fraction
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class Kernel:
22
+ name: str
23
+ kind: str # "cubic" (knot at q=1/2) or "wendland" (single poly)
24
+ c2_pi: Fraction # C2 = c2_pi / pi
25
+ c3_pi: Fraction # C3 = c3_pi / pi
26
+ n: int = 0 # Wendland: power of (1-q)
27
+ poly: tuple = () # Wendland: ((power, coeff), ...) of P(q), c in Q
28
+
29
+ def w_hat(self, q: Fraction) -> Fraction:
30
+ """Exact kernel shape at a rational q (symbolic checks)."""
31
+ if self.kind == "cubic":
32
+ if q <= Fraction(1, 2):
33
+ return (1 - q) ** 3 - 4 * (Fraction(1, 2) - q) ** 3
34
+ return (1 - q) ** 3
35
+ v = Fraction(0)
36
+ for p, c in self.poly:
37
+ v += c * q ** p
38
+ return (1 - q) ** self.n * v
39
+
40
+
41
+ KERNELS = {
42
+ "cubic": Kernel("cubic", "cubic", Fraction(80, 7), Fraction(16)),
43
+ "w2": Kernel("w2", "wendland", Fraction(7), Fraction(21, 2), 4,
44
+ ((0, Fraction(1)), (1, Fraction(4)))),
45
+ "w4": Kernel("w4", "wendland", Fraction(9), Fraction(495, 32), 6,
46
+ ((0, Fraction(1)), (1, Fraction(6)), (2, Fraction(35, 3)))),
47
+ "w6": Kernel("w6", "wendland", Fraction(78, 7), Fraction(1365, 64), 8,
48
+ ((0, Fraction(1)), (1, Fraction(8)),
49
+ (2, Fraction(25)), (3, Fraction(32)))),
50
+ }
51
+
52
+
53
+ def kernel(name: str) -> Kernel:
54
+ try:
55
+ return KERNELS[name]
56
+ except KeyError:
57
+ raise KeyError(f"unknown kernel {name!r}; expected one of {sorted(KERNELS)}")
@@ -0,0 +1,133 @@
1
+ """Independent high-precision quadrature oracles.
2
+
3
+ These re-derive lambda directly from the defining integrals (no shared code
4
+ with symbolic.py) so they can validate the exported closed forms:
5
+
6
+ 3-D planar : lambda = Int_d^1 C3 W(q) 2 pi q (q-d) dq
7
+ 2-D planar : lambda = Int_d^1 C2 W(q) 2 q arccos(d/q) dq
8
+ sphere : lambda = Int_d^{qmax} C3 W(q) pi q/D (R^2-D^2+2Dq-q^2) dq,
9
+ D = R+d, qmax = min(1, 2R+d)
10
+ 2-D Cartesian: true double integral over the support disk cut by the
11
+ chord (no 1-D reduction) -- the most independent check.
12
+
13
+ Unit support h=1, particle outside the boundary, d in [0,1].
14
+ """
15
+ from fractions import Fraction
16
+
17
+ import mpmath as mp
18
+
19
+ from .kernels import kernel
20
+
21
+
22
+ def _to_mpf(x) -> mp.mpf:
23
+ """int / Fraction / str / mpf -> mp.mpf (this mpmath build rejects Fraction)."""
24
+ if isinstance(x, Fraction):
25
+ return mp.mpf(x.numerator) / mp.mpf(x.denominator)
26
+ return mp.mpf(x)
27
+
28
+
29
+ def _c2(kd) -> mp.mpf:
30
+ return (mp.mpf(kd.c2_pi.numerator) / mp.mpf(kd.c2_pi.denominator)) / mp.pi
31
+
32
+
33
+ def _c3(kd) -> mp.mpf:
34
+ return (mp.mpf(kd.c3_pi.numerator) / mp.mpf(kd.c3_pi.denominator)) / mp.pi
35
+
36
+
37
+ def _w_hat_mp(kd, q: mp.mpf) -> mp.mpf:
38
+ if kd.kind == "cubic":
39
+ if q <= mp.mpf(1) / 2:
40
+ return (1 - q) ** 3 - 4 * (mp.mpf(1) / 2 - q) ** 3
41
+ return (1 - q) ** 3
42
+ v = mp.mpf(0)
43
+ for p, c in kd.poly:
44
+ v += (mp.mpf(c.numerator) / mp.mpf(c.denominator)) * q ** p
45
+ return (1 - q) ** kd.n * v
46
+
47
+
48
+ def quad_planar3d(kernel_name: str, d, dps: int = 40) -> mp.mpf:
49
+ kd = kernel(kernel_name)
50
+ c3 = _c3(kd)
51
+ d = _to_mpf(d)
52
+ f = lambda q: c3 * _w_hat_mp(kd, q) * 2 * mp.pi * q * (q - d)
53
+ if kd.kind == "cubic" and d < mp.mpf(1) / 2:
54
+ return mp.quad(f, [d, mp.mpf(1) / 2, 1])
55
+ return mp.quad(f, [d, 1])
56
+
57
+
58
+ def quad_planar2d(kernel_name: str, d, dps: int = 40) -> mp.mpf:
59
+ kd = kernel(kernel_name)
60
+ c2 = _c2(kd)
61
+ d = _to_mpf(d)
62
+ f = lambda q: c2 * _w_hat_mp(kd, q) * 2 * q * mp.acos(d / q)
63
+ if kd.kind == "cubic" and d < mp.mpf(1) / 2:
64
+ return mp.quad(f, [d, mp.mpf(1) / 2, 1])
65
+ return mp.quad(f, [d, 1])
66
+
67
+
68
+ def quad_sphere(kernel_name: str, R, d, dps: int = 40) -> mp.mpf:
69
+ kd = kernel(kernel_name)
70
+ c3 = _c3(kd)
71
+ R = _to_mpf(R)
72
+ d = _to_mpf(d)
73
+ D = R + d
74
+ qmax = min(mp.mpf(1), 2 * R + d)
75
+ f = (lambda q: c3 * _w_hat_mp(kd, q)
76
+ * mp.pi * q / D * (R ** 2 - D ** 2 + 2 * D * q - q ** 2))
77
+ return mp.quad(f, [d, qmax])
78
+
79
+
80
+ def quad_planar2d_cartesian(kernel_name: str, d, dps: int = 30) -> mp.mpf:
81
+ """True 2-D Cartesian quadrature (no 1-D shell reduction).
82
+
83
+ Particle at the origin, boundary line y = d, boundary side y > d:
84
+ lambda = Int_{x} Int_{y=d}^{sqrt(1-x^2)} C2 W_hat(sqrt(x^2+y^2)) dy dx
85
+ over |x| <= sqrt(1-d^2). For the cubic spline the knot circle r = 1/2 is
86
+ added as a breakpoint wherever it crosses the inner interval.
87
+ """
88
+ kd = kernel(kernel_name)
89
+ c2 = _c2(kd)
90
+ d = _to_mpf(d)
91
+ xmax = mp.sqrt(1 - d ** 2)
92
+
93
+ def inner(x: mp.mpf) -> mp.mpf:
94
+ ytop = mp.sqrt(1 - x ** 2)
95
+ pts = [d]
96
+ if kd.kind == "cubic" and x ** 2 < mp.mpf(1) / 4:
97
+ yk = mp.sqrt(mp.mpf(1) / 4 - x ** 2)
98
+ if d < yk < ytop:
99
+ pts.append(yk)
100
+ pts.append(ytop)
101
+ g = lambda y: c2 * _w_hat_mp(kd, mp.sqrt(x ** 2 + y ** 2))
102
+ return mp.quad(g, pts)
103
+
104
+ # dense outer breakpoints: inner(x) ~ sqrt(xmax - |x|) at the endpoints
105
+ # (the chord degenerates), which a 3-way split resolves only to ~3e-15
106
+ # for the cubic spline kernel (observed)
107
+ n = 16
108
+ return mp.quad(inner, [-xmax + 2 * xmax * mp.mpf(i) / n for i in range(n + 1)])
109
+
110
+
111
+ def physical_planar3d(kernel_name: str, d, h, dps: int = 30) -> mp.mpf:
112
+ """The PHYSICAL 3-D integral with explicit support radius h:
113
+
114
+ Int_{r=d}^{h} (C3/h^3) W_hat(r/h) 2 pi r (r-d) dr
115
+
116
+ which by the substitution r = h q must equal lambda_3(d/h). Used for the
117
+ h-scaling test.
118
+ """
119
+ kd = kernel(kernel_name)
120
+ c3 = _c3(kd)
121
+ d = _to_mpf(d)
122
+ h = _to_mpf(h)
123
+ f = (lambda r: (c3 / h ** 3) * _w_hat_mp(kd, r / h)
124
+ * 2 * mp.pi * r * (r - d))
125
+ if kd.kind == "cubic" and d < h / 2:
126
+ return mp.quad(f, [d, h / 2, h])
127
+ return mp.quad(f, [d, h])
128
+
129
+
130
+ __all__ = [
131
+ "quad_planar3d", "quad_planar2d", "quad_sphere",
132
+ "quad_planar2d_cartesian", "physical_planar3d",
133
+ ]