simgeometry 0.1.0__tar.gz → 0.2.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 (94) hide show
  1. {simgeometry-0.1.0/src/simgeometry.egg-info → simgeometry-0.2.0}/PKG-INFO +76 -13
  2. simgeometry-0.2.0/README.md +126 -0
  3. {simgeometry-0.1.0 → simgeometry-0.2.0}/pyproject.toml +24 -8
  4. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/__init__.py +26 -1
  5. simgeometry-0.2.0/src/simgeometry/_decomposition_views.py +181 -0
  6. simgeometry-0.2.0/src/simgeometry/_level_set.py +198 -0
  7. simgeometry-0.2.0/src/simgeometry/_profiling.py +8 -0
  8. simgeometry-0.2.0/src/simgeometry/_rounded_box.py +125 -0
  9. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/api.py +36 -13
  10. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/boundingbox.py +1 -1
  11. simgeometry-0.2.0/src/simgeometry/collection.py +205 -0
  12. simgeometry-0.2.0/src/simgeometry/crossings.py +532 -0
  13. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/cut_edges.py +263 -10
  14. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/cylindrical.py +1 -1
  15. simgeometry-0.2.0/src/simgeometry/domain_decomposition.py +913 -0
  16. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/evaluation.py +39 -1
  17. simgeometry-0.2.0/src/simgeometry/fractions.py +544 -0
  18. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/grid.py +19 -6
  19. simgeometry-0.2.0/src/simgeometry/kernels/__init__.py +23 -0
  20. simgeometry-0.2.0/src/simgeometry/kernels/absorb_polygons_2d/__init__.py +16 -0
  21. simgeometry-0.2.0/src/simgeometry/kernels/absorb_polygons_2d/absorb_polygons_2d_cuda.cu +63 -0
  22. simgeometry-0.2.0/src/simgeometry/kernels/absorb_polygons_2d/absorb_polygons_2d_numpy.py +65 -0
  23. simgeometry-0.2.0/src/simgeometry/kernels/absorb_polygons_2d/absorb_polygons_2d_pyccel.py +152 -0
  24. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_ball/__init__.py +2 -4
  25. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_ball/contains_ball_metal.py +3 -3
  26. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_box/__init__.py +2 -4
  27. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_box/contains_box_metal.py +3 -3
  28. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_polygon_2d/__init__.py +2 -4
  29. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_polygon_2d/contains_polygon_2d_metal.py +3 -3
  30. simgeometry-0.2.0/src/simgeometry/kernels/polygon_hit.cuh +56 -0
  31. simgeometry-0.2.0/src/simgeometry/kernels/polygon_hits.py +139 -0
  32. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/mesh.py +43 -31
  33. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/__init__.py +12 -0
  34. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/aperture2d.py +38 -12
  35. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/aperture3d.py +15 -1
  36. simgeometry-0.2.0/src/simgeometry/objects/axisymmetric.py +239 -0
  37. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/circle.py +47 -4
  38. simgeometry-0.2.0/src/simgeometry/objects/cone.py +143 -0
  39. simgeometry-0.2.0/src/simgeometry/objects/cuboid.py +275 -0
  40. simgeometry-0.2.0/src/simgeometry/objects/cylinder.py +134 -0
  41. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/function_object.py +11 -8
  42. simgeometry-0.2.0/src/simgeometry/objects/helix.py +335 -0
  43. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/line.py +40 -3
  44. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/polygon.py +51 -3
  45. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/polyline.py +2 -2
  46. simgeometry-0.2.0/src/simgeometry/objects/rectangle.py +263 -0
  47. simgeometry-0.2.0/src/simgeometry/objects/slanted_tube.py +379 -0
  48. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/sphere.py +40 -3
  49. simgeometry-0.2.0/src/simgeometry/objects/torus.py +125 -0
  50. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/triangle.py +57 -3
  51. simgeometry-0.2.0/src/simgeometry/objects/tube.py +167 -0
  52. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/operations.py +344 -34
  53. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/panels.py +16 -5
  54. simgeometry-0.2.0/src/simgeometry/polygon_objects.py +244 -0
  55. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/queries.py +272 -114
  56. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/sampling.py +4 -0
  57. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/shapes.py +111 -16
  58. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/simulation_domain.py +17 -0
  59. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/visualization.py +418 -167
  60. {simgeometry-0.1.0 → simgeometry-0.2.0/src/simgeometry.egg-info}/PKG-INFO +76 -13
  61. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry.egg-info/SOURCES.txt +20 -5
  62. simgeometry-0.2.0/src/simgeometry.egg-info/entry_points.txt +2 -0
  63. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry.egg-info/requires.txt +7 -2
  64. simgeometry-0.1.0/LICENSE.txt +0 -21
  65. simgeometry-0.1.0/README.md +0 -65
  66. simgeometry-0.1.0/src/simgeometry/collection.py +0 -65
  67. simgeometry-0.1.0/src/simgeometry/crossings.py +0 -319
  68. simgeometry-0.1.0/src/simgeometry/domain_decomposition.py +0 -496
  69. simgeometry-0.1.0/src/simgeometry/fractions.py +0 -68
  70. simgeometry-0.1.0/src/simgeometry/kernels/__init__.py +0 -1
  71. simgeometry-0.1.0/src/simgeometry/kernels/_build.py +0 -53
  72. simgeometry-0.1.0/src/simgeometry/kernels/_metal.py +0 -41
  73. simgeometry-0.1.0/src/simgeometry/kernels/compile.py +0 -24
  74. simgeometry-0.1.0/src/simgeometry/objects/cuboid.py +0 -173
  75. simgeometry-0.1.0/src/simgeometry/objects/rectangle.py +0 -130
  76. {simgeometry-0.1.0 → simgeometry-0.2.0}/setup.cfg +0 -0
  77. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/_bounds.py +0 -0
  78. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/_normalize.py +0 -0
  79. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/_particle_boundaries.py +0 -0
  80. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/_types.py +0 -0
  81. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_ball/contains_ball_cuda.cu +0 -0
  82. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_ball/contains_ball_numpy.py +0 -0
  83. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_ball/contains_ball_pyccel.py +0 -0
  84. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_box/contains_box_cuda.cu +0 -0
  85. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_box/contains_box_numpy.py +0 -0
  86. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_box/contains_box_pyccel.py +0 -0
  87. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_polygon_2d/contains_polygon_2d_cuda.cu +0 -0
  88. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_polygon_2d/contains_polygon_2d_numpy.py +0 -0
  89. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_polygon_2d/contains_polygon_2d_pyccel.py +0 -0
  90. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/plotting.py +0 -0
  91. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/py.typed +0 -0
  92. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/utils.py +0 -0
  93. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry.egg-info/dependency_links.txt +0 -0
  94. {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry.egg-info/top_level.txt +0 -0
@@ -1,9 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simgeometry
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Shapes, grids, and simulation domains for numerical simulations
5
5
  Author: Max
6
- License-Expression: MIT
7
6
  Project-URL: Source, https://github.com/max-models/simgeometry
8
7
  Keywords: geometry,grids,simulation,mpi
9
8
  Classifier: Development Status :: 3 - Alpha
@@ -15,11 +14,12 @@ Classifier: Programming Language :: Python :: 3.13
15
14
  Classifier: Programming Language :: Python :: 3.14
16
15
  Requires-Python: >=3.10
17
16
  Description-Content-Type: text/markdown
18
- License-File: LICENSE.txt
19
- Requires-Dist: cunumpy>=0.6.2
17
+ Requires-Dist: cunumpy>=0.7.1
20
18
  Requires-Dist: maybempi>=0.1
19
+ Requires-Dist: methodcard
21
20
  Requires-Dist: mpiarray>=0.1.0
22
21
  Requires-Dist: numpy
22
+ Requires-Dist: scope-profiler>=0.7.5
23
23
  Provides-Extra: dev
24
24
  Requires-Dist: jupyterlab; extra == "dev"
25
25
  Requires-Dist: pre-commit; extra == "dev"
@@ -29,7 +29,8 @@ Requires-Dist: ruff; extra == "dev"
29
29
  Requires-Dist: simgeometry[docs,test]; extra == "dev"
30
30
  Provides-Extra: docs
31
31
  Requires-Dist: griffe; extra == "docs"
32
- Requires-Dist: simgeometry[clip,plot]; extra == "docs"
32
+ Requires-Dist: simgeometry[clip,interactive]; extra == "docs"
33
+ Requires-Dist: plasma-plots[scene]>=0.2.5; extra == "docs"
33
34
  Requires-Dist: ipykernel; extra == "docs"
34
35
  Requires-Dist: matplotlib>=3.10.9; extra == "docs"
35
36
  Requires-Dist: nbclient>=0.11; extra == "docs"
@@ -44,6 +45,7 @@ Provides-Extra: plot
44
45
  Requires-Dist: matplotlib>=3.10; extra == "plot"
45
46
  Requires-Dist: simgeometry[evaluate,mesh]; extra == "plot"
46
47
  Provides-Extra: interactive
48
+ Requires-Dist: plasma-plots>=0.2.5; extra == "interactive"
47
49
  Requires-Dist: pyvista>=0.44; extra == "interactive"
48
50
  Requires-Dist: simgeometry[plot]; extra == "interactive"
49
51
  Provides-Extra: mpi
@@ -55,11 +57,11 @@ Requires-Dist: cupy-cuda12x; extra == "gpu"
55
57
  Provides-Extra: metal
56
58
  Requires-Dist: mlx; (sys_platform == "darwin" and platform_machine == "arm64") and extra == "metal"
57
59
  Provides-Extra: test
60
+ Requires-Dist: methodcard[symbolic]; extra == "test"
58
61
  Requires-Dist: mpi4py; extra == "test"
59
62
  Requires-Dist: pytest; extra == "test"
60
63
  Requires-Dist: pytest-cov; extra == "test"
61
64
  Requires-Dist: simgeometry[clip]; extra == "test"
62
- Dynamic: license-file
63
65
 
64
66
  # simgeometry
65
67
 
@@ -77,12 +79,12 @@ pip install simgeometry
77
79
  ```
78
80
 
79
81
  For development, install this checkout with `pip install -e '.[test]'`.
80
- NumPy, cunumpy, maybempi, and mpiarray are runtime dependencies.
81
- Matplotlib is optional. Install `simgeometry[plot]` for plotting,
82
- labelled evaluation and surface meshing, or `simgeometry[interactive]`
83
- for interactive 3D clipping with PyVista. Install `simgeometry[mpi]` for
84
- MPI, `simgeometry[pyccel]` for compiled CPU containment kernels, or
85
- `simgeometry[gpu]` for CUDA.
82
+ NumPy, cunumpy, maybempi, mpiarray and scope-profiler are runtime
83
+ dependencies. Matplotlib is optional. Install `simgeometry[plot]` for
84
+ plotting, labelled evaluation and surface meshing, or
85
+ `simgeometry[interactive]` for interactive 3D clipping with PyVista.
86
+ Install `simgeometry[mpi]` for MPI, `simgeometry[pyccel]` for compiled
87
+ CPU containment kernels, or `simgeometry[gpu]` for CUDA.
86
88
 
87
89
  ## Example
88
90
 
@@ -115,10 +117,71 @@ inside = shell.evaluate(x=[0, 0.8], y=0, z=0)
115
117
  Shapes support named collections, Boolean operations, affine transforms,
116
118
  surface queries, point sampling, mesh export, cut-edge geometry and
117
119
  approximate cell/face fractions. See the [executed
118
- tutorials](https://max-models.github.io/simgeometry/tutorials/) and [API
120
+ examples](https://max-models.github.io/simgeometry/examples/) and [API
119
121
  guide](https://max-models.github.io/simgeometry/getting-started/geometry-api/)
120
122
  for examples and the accuracy limits of sampled operations.
121
123
 
124
+ ## Level sets, solids of revolution and conductors
125
+
126
+ ``` python
127
+ from simgeometry import GeometryCollection, Tube
128
+
129
+ ring = lambda z: Tube((0, 0, z - 0.4), (0, 0, z + 0.4), 1.0, 1.6, fillet=0.1)
130
+ lens = GeometryCollection(
131
+ {"entrance": ring(-1.2), "focus": ring(0.0), "exit": ring(1.2)},
132
+ voltages={"entrance": 0.0, "focus": 10e3, "exit": 0.0},
133
+ )
134
+ scan = lens.with_voltages(focus=lambda t: 10e3 * min(t / 1e-6, 1.0))
135
+ values = scan.dirichlet_values(grid, t=0.5e-6) # labels and voltages per node
136
+ distance = lens["focus"].level_set(points) # exact signed distance
137
+ ```
138
+
139
+ `Cylinder`, `Tube`, `Cone` and `Torus` take any axis. Analytic shapes
140
+ have a `level_set(points)`, negative inside, with `level_set_exact`
141
+ where it is the Euclidean signed distance; Booleans and transforms
142
+ propagate it. Edge crossings refine it with a few secant steps instead
143
+ of bisecting `contains`, and `signed_distance` returns an exact one
144
+ directly. The [conductors
145
+ example](https://max-models.github.io/simgeometry/examples/conductors-and-level-sets/)
146
+ builds an Einzel lens.
147
+
148
+ ## Preview a domain decomposition
149
+
150
+ ``` python
151
+ from simgeometry import Sphere
152
+ from simgeometry.domain_decomposition import DomainDecomposition
153
+
154
+ preview = DomainDecomposition.preview(8, decompose=[True, True, True])
155
+ preview.proc_sizes # [2, 2, 2], as on 8 MPI ranks
156
+ boxes = preview.subdomain_boxes((0, 0, 0), (2, 1, 1), (33, 17, 17))
157
+ preview.show_3d((0, 0, 0), (2, 1, 1), (33, 17, 17), shape=Sphere((1, 0.5, 0.5), 0.35))
158
+ ```
159
+
160
+ `DomainDecomposition.preview(size)` computes the split a `size`-rank run
161
+ would use without a communicator, for inspection and planning: index
162
+ ranges, subdomains and owner ranks for every rank. `show_3d` draws one
163
+ box per rank (with `simgeometry[interactive]`) and `show` one rectangle
164
+ per rank of a 2D decomposition. See the [parallel grids
165
+ example](https://max-models.github.io/simgeometry/examples/parallel-grids/).
166
+
167
+ ## Profiling
168
+
169
+ simgeometry ships
170
+ [scope-profiler](https://github.com/max-models/scope-profiler) regions
171
+ for its expensive operations (`simgeometry.node_mask`,
172
+ `simgeometry.crossings`, `simgeometry.to_mesh`, …). They are off by
173
+ default and cost almost nothing. Turn them on in an application that
174
+ profiles itself with the `simgeometry` tag:
175
+
176
+ ``` python
177
+ import scope_profiler as sp
178
+
179
+ sp.setup(enable_tags=["simgeometry"])
180
+ ```
181
+
182
+ or `SCOPE_PROFILER_ENABLE_TAGS=simgeometry`, or
183
+ `scope-profiler run --enable-tag simgeometry`.
184
+
122
185
  ## Development
123
186
 
124
187
  ``` bash
@@ -0,0 +1,126 @@
1
+ # simgeometry
2
+
3
+
4
+ Geometry primitives for numerical simulations: bounded domains,
5
+ structured grids, MPI domain decomposition, and 1D, 2D, and 3D shapes.
6
+ Box, ball, and polygon containment have NumPy, Pyccel, CUDA, and Metal
7
+ implementations. Originally developed for
8
+ [Picnic](https://github.com/max-models/picnic).
9
+
10
+ ## Install
11
+
12
+ ``` bash
13
+ pip install simgeometry
14
+ ```
15
+
16
+ For development, install this checkout with `pip install -e '.[test]'`.
17
+ NumPy, cunumpy, maybempi, mpiarray and scope-profiler are runtime
18
+ dependencies. Matplotlib is optional. Install `simgeometry[plot]` for
19
+ plotting, labelled evaluation and surface meshing, or
20
+ `simgeometry[interactive]` for interactive 3D clipping with PyVista.
21
+ Install `simgeometry[mpi]` for MPI, `simgeometry[pyccel]` for compiled
22
+ CPU containment kernels, or `simgeometry[gpu]` for CUDA.
23
+
24
+ ## Example
25
+
26
+ ``` python
27
+ from simgeometry.grid import Grid
28
+ from simgeometry.objects import Circle
29
+
30
+ boundary = Circle(midpoint=(0.5, 0.5), radius=0.25)
31
+ grid = Grid(ndim=2, lower_bound=(0, 0), upper_bound=(1, 1), num_gridpoints=(32, 32))
32
+ mask = boundary.node_mask(grid)
33
+ ```
34
+
35
+ `SimulationDomain` accepts field and particle boundary objects through
36
+ their `get_bc(dir=..., side=...).bc_type` interface. Applications may
37
+ supply a seeded random function with `BoundingBox.random` and an
38
+ accelerated particle boundary implementation with
39
+ `SimulationDomain.boundary_kernel`.
40
+
41
+ ## Inspect, slice, and compose
42
+
43
+ ``` python
44
+ from simgeometry import Sphere
45
+
46
+ shell = Sphere((0, 0, 0), 1) - Sphere((0, 0, 0), 0.4)
47
+ shell.show_3d(opacity=0.6)
48
+ shell.slice(z=0.2).show()
49
+ inside = shell.evaluate(x=[0, 0.8], y=0, z=0)
50
+ ```
51
+
52
+ Shapes support named collections, Boolean operations, affine transforms,
53
+ surface queries, point sampling, mesh export, cut-edge geometry and
54
+ approximate cell/face fractions. See the [executed
55
+ examples](https://max-models.github.io/simgeometry/examples/) and [API
56
+ guide](https://max-models.github.io/simgeometry/getting-started/geometry-api/)
57
+ for examples and the accuracy limits of sampled operations.
58
+
59
+ ## Level sets, solids of revolution and conductors
60
+
61
+ ``` python
62
+ from simgeometry import GeometryCollection, Tube
63
+
64
+ ring = lambda z: Tube((0, 0, z - 0.4), (0, 0, z + 0.4), 1.0, 1.6, fillet=0.1)
65
+ lens = GeometryCollection(
66
+ {"entrance": ring(-1.2), "focus": ring(0.0), "exit": ring(1.2)},
67
+ voltages={"entrance": 0.0, "focus": 10e3, "exit": 0.0},
68
+ )
69
+ scan = lens.with_voltages(focus=lambda t: 10e3 * min(t / 1e-6, 1.0))
70
+ values = scan.dirichlet_values(grid, t=0.5e-6) # labels and voltages per node
71
+ distance = lens["focus"].level_set(points) # exact signed distance
72
+ ```
73
+
74
+ `Cylinder`, `Tube`, `Cone` and `Torus` take any axis. Analytic shapes
75
+ have a `level_set(points)`, negative inside, with `level_set_exact`
76
+ where it is the Euclidean signed distance; Booleans and transforms
77
+ propagate it. Edge crossings refine it with a few secant steps instead
78
+ of bisecting `contains`, and `signed_distance` returns an exact one
79
+ directly. The [conductors
80
+ example](https://max-models.github.io/simgeometry/examples/conductors-and-level-sets/)
81
+ builds an Einzel lens.
82
+
83
+ ## Preview a domain decomposition
84
+
85
+ ``` python
86
+ from simgeometry import Sphere
87
+ from simgeometry.domain_decomposition import DomainDecomposition
88
+
89
+ preview = DomainDecomposition.preview(8, decompose=[True, True, True])
90
+ preview.proc_sizes # [2, 2, 2], as on 8 MPI ranks
91
+ boxes = preview.subdomain_boxes((0, 0, 0), (2, 1, 1), (33, 17, 17))
92
+ preview.show_3d((0, 0, 0), (2, 1, 1), (33, 17, 17), shape=Sphere((1, 0.5, 0.5), 0.35))
93
+ ```
94
+
95
+ `DomainDecomposition.preview(size)` computes the split a `size`-rank run
96
+ would use without a communicator, for inspection and planning: index
97
+ ranges, subdomains and owner ranks for every rank. `show_3d` draws one
98
+ box per rank (with `simgeometry[interactive]`) and `show` one rectangle
99
+ per rank of a 2D decomposition. See the [parallel grids
100
+ example](https://max-models.github.io/simgeometry/examples/parallel-grids/).
101
+
102
+ ## Profiling
103
+
104
+ simgeometry ships
105
+ [scope-profiler](https://github.com/max-models/scope-profiler) regions
106
+ for its expensive operations (`simgeometry.node_mask`,
107
+ `simgeometry.crossings`, `simgeometry.to_mesh`, …). They are off by
108
+ default and cost almost nothing. Turn them on in an application that
109
+ profiles itself with the `simgeometry` tag:
110
+
111
+ ``` python
112
+ import scope_profiler as sp
113
+
114
+ sp.setup(enable_tags=["simgeometry"])
115
+ ```
116
+
117
+ or `SCOPE_PROFILER_ENABLE_TAGS=simgeometry`, or
118
+ `scope-profiler run --enable-tag simgeometry`.
119
+
120
+ ## Development
121
+
122
+ ``` bash
123
+ pytest src/simgeometry/tests
124
+ ```
125
+
126
+ Documentation: <https://max-models.github.io/simgeometry/>
@@ -4,12 +4,10 @@ requires = [ "setuptools", "wheel" ]
4
4
 
5
5
  [project]
6
6
  name = "simgeometry"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Shapes, grids, and simulation domains for numerical simulations"
9
9
  readme = "README.md"
10
10
  keywords = [ "geometry", "grids", "simulation", "mpi" ]
11
- license = "MIT"
12
- license-files = [ "LICENSE.txt" ]
13
11
  authors = [ { name = "Max" } ]
14
12
  requires-python = ">=3.10"
15
13
  classifiers = [
@@ -22,10 +20,12 @@ classifiers = [
22
20
  "Programming Language :: Python :: 3.14",
23
21
  ]
24
22
  dependencies = [
25
- "cunumpy>=0.6.2",
23
+ "cunumpy>=0.7.1",
26
24
  "maybempi>=0.1",
25
+ "methodcard",
27
26
  "mpiarray>=0.1.0",
28
27
  "numpy",
28
+ "scope-profiler>=0.7.5",
29
29
  ]
30
30
  optional-dependencies.dev = [
31
31
  "jupyterlab",
@@ -37,7 +37,9 @@ optional-dependencies.dev = [
37
37
  ]
38
38
  optional-dependencies.docs = [
39
39
  "griffe",
40
- "simgeometry[clip,plot]",
40
+ "simgeometry[clip,interactive]",
41
+ # interactive 3-D scenes in the notebooks (plasma_plots.scene)
42
+ "plasma-plots[scene]>=0.2.5",
41
43
  "ipykernel",
42
44
  "matplotlib>=3.10.9",
43
45
  "nbclient>=0.11",
@@ -47,17 +49,20 @@ optional-dependencies.clip = [ "shapely>=2" ]
47
49
  optional-dependencies.evaluate = [ "xarray" ]
48
50
  optional-dependencies.mesh = [ "scikit-image>=0.22" ]
49
51
  optional-dependencies.plot = [ "matplotlib>=3.10", "simgeometry[evaluate,mesh]" ]
50
- optional-dependencies.interactive = [ "pyvista>=0.44", "simgeometry[plot]" ]
52
+ optional-dependencies.interactive = [ "plasma-plots>=0.2.5", "pyvista>=0.44", "simgeometry[plot]" ]
51
53
  optional-dependencies.mpi = [ "mpi4py" ]
52
54
  optional-dependencies.pyccel = [ "pyccel>2.0" ]
53
55
  optional-dependencies.gpu = [ "cupy-cuda12x" ]
54
56
  optional-dependencies.metal = [ "mlx; sys_platform == 'darwin' and platform_machine == 'arm64'" ]
55
- optional-dependencies.test = [ "mpi4py", "pytest", "pytest-cov", "simgeometry[clip]" ]
57
+ optional-dependencies.test = [ "methodcard[symbolic]", "mpi4py", "pytest", "pytest-cov", "simgeometry[clip]" ]
56
58
  urls.Source = "https://github.com/max-models/simgeometry"
57
59
 
60
+ [project.entry-points."scope_profiler.opt_in_tags"]
61
+ simgeometry = "simgeometry"
62
+
58
63
  [tool.setuptools]
59
64
  package-data."*" = [ "py.typed" ]
60
- package-data.simgeometry = [ "kernels/*/*.cu" ]
65
+ package-data.simgeometry = [ "kernels/*.cuh", "kernels/*/*.cu" ]
61
66
  packages.find.where = [ "src" ]
62
67
  packages.find.exclude = [ "simgeometry.tests*" ]
63
68
 
@@ -83,8 +88,19 @@ reportMissingImports = "error"
83
88
  ini_options.pythonpath = [ "src" ]
84
89
  ini_options.testpaths = [ "src" ]
85
90
  ini_options.addopts = "-ra"
91
+ ini_options.markers = [ "convergence: an order-of-accuracy test of a method card" ]
86
92
 
87
93
  [tool.coverage]
88
94
  run.omit = [ "*/tests/*" ]
89
95
  run.source = [ "simgeometry" ]
90
96
  report.show_missing = true
97
+
98
+ # PRs to devel test against the devel branches of the max-models packages (pinned in
99
+ # uv.lock); PRs to main and the release use PyPI only (CI sets UV_NO_SOURCES).
100
+ [tool.uv.sources]
101
+ cunumpy = { git = "https://github.com/max-models/cunumpy", branch = "devel" }
102
+ maybempi = { git = "https://github.com/max-models/maybempi", branch = "devel" }
103
+ methodcard = { git = "https://github.com/max-models/methodcard", branch = "devel" }
104
+ mpiarray = { git = "https://github.com/max-models/mpiarray", branch = "devel" }
105
+ plasma-plots = { git = "https://github.com/max-models/plasma-plots", branch = "devel" }
106
+ scope-profiler = { git = "https://github.com/max-models/scope-profiler", branch = "devel" }
@@ -1,38 +1,63 @@
1
1
  """Shapes, grids, and domains for numerical simulations."""
2
2
 
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ import scope_profiler as sp
6
+
7
+ from simgeometry._profiling import _PROFILE_TAGS
3
8
  from simgeometry.collection import GeometryCollection
4
9
  from simgeometry.grid import Grid
5
10
  from simgeometry.objects import (
6
11
  Aperture2D,
7
12
  Aperture3D,
8
13
  Circle,
14
+ Cone,
9
15
  Cuboid,
16
+ Cylinder,
10
17
  FunctionObject2D,
11
18
  FunctionObject3D,
19
+ Helix,
12
20
  Line,
13
21
  Polygon,
14
22
  Polyline,
15
23
  Rectangle,
24
+ SlantedTube,
16
25
  Sphere,
26
+ Torus,
17
27
  Triangle,
28
+ Tube,
18
29
  )
30
+ from simgeometry.operations import PolarRepeat
19
31
  from simgeometry.simulation_domain import SimulationDomain
20
32
 
21
- __version__ = "0.1.0"
33
+ # simgeometry's regions stay off unless the application enables the "simgeometry" tag.
34
+ sp.register_opt_in_tag(*_PROFILE_TAGS)
35
+
36
+ try:
37
+ __version__ = version("simgeometry")
38
+ except PackageNotFoundError: # not installed, e.g. run from a source tree
39
+ __version__ = "0.0.0+unknown"
22
40
  __all__ = [
23
41
  "GeometryCollection",
24
42
  "Grid",
43
+ "PolarRepeat",
25
44
  "SimulationDomain",
26
45
  "Aperture2D",
27
46
  "Aperture3D",
28
47
  "Circle",
48
+ "Cone",
29
49
  "Cuboid",
50
+ "Cylinder",
30
51
  "FunctionObject2D",
31
52
  "FunctionObject3D",
53
+ "Helix",
32
54
  "Line",
33
55
  "Polygon",
34
56
  "Polyline",
35
57
  "Rectangle",
58
+ "SlantedTube",
36
59
  "Sphere",
60
+ "Torus",
37
61
  "Triangle",
62
+ "Tube",
38
63
  ]
@@ -0,0 +1,181 @@
1
+ """Views of a domain decomposition: one box (3D) or rectangle (2D) per rank.
2
+
3
+ Used by `DomainDecomposition.show_3d` and `DomainDecomposition.show`.
4
+ Nothing here communicates; the boxes come from ``subdomain_boxes``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ import numpy as np
12
+
13
+ _MAX_LEGEND_RANKS = 16
14
+
15
+
16
+ def _rank_colors(size: int) -> list[tuple[float, float, float]]:
17
+ """Return one distinct colour per rank: tab10 up to 10 ranks, else tab20/turbo."""
18
+ from matplotlib import colormaps
19
+
20
+ if size <= 10:
21
+ cmap = colormaps["tab10"]
22
+ return [tuple(cmap(i)[:3]) for i in range(size)]
23
+ if size <= 20:
24
+ cmap = colormaps["tab20"]
25
+ return [tuple(cmap(i)[:3]) for i in range(size)]
26
+ cmap = colormaps["turbo"]
27
+ return [tuple(cmap(i / (size - 1))[:3]) for i in range(size)]
28
+
29
+
30
+ def _objects(shape: Any) -> dict[str, Any]:
31
+ """Return the named objects of a collection, or the shape itself."""
32
+ return dict(getattr(shape, "named_objects", None) or {"shape": shape})
33
+
34
+
35
+ def show_decomposition_3d(
36
+ decomposition: Any,
37
+ lower_bound: Any,
38
+ upper_bound: Any,
39
+ num_gridpoints: Any,
40
+ *,
41
+ shape: Any = None,
42
+ opacity: float = 0.25,
43
+ shape_opacity: float = 1.0,
44
+ resolution: int = 48,
45
+ legend: bool | None = None,
46
+ plotter: Any = None,
47
+ display: bool = True,
48
+ save: Any = None,
49
+ ) -> Any:
50
+ """Draw the subdomains of a 3D decomposition with plasma-plots; return the plotter."""
51
+ if decomposition.ndim != 3:
52
+ raise ValueError("show_3d needs a three-dimensional decomposition")
53
+ try:
54
+ import pyvista as pv
55
+ from plasma_plots.pyvista_plots import pyvista_surfaces
56
+ except ImportError as exc:
57
+ raise ImportError("Interactive 3D needs simgeometry[interactive]") from exc
58
+
59
+ boxes = decomposition.subdomain_boxes(lower_bound, upper_bound, num_gridpoints)
60
+ if legend is None:
61
+ legend = len(boxes) <= _MAX_LEGEND_RANKS
62
+ meshes = {}
63
+ for rank, (lower, upper) in enumerate(boxes):
64
+ box = pv.Box(
65
+ bounds=(lower[0], upper[0], lower[1], upper[1], lower[2], upper[2]),
66
+ quads=True,
67
+ )
68
+ box.point_data["rank"] = np.full(box.n_points, rank)
69
+ meshes[f"rank {rank}"] = box
70
+ if plotter is None:
71
+ plotter = pv.Plotter(off_screen=not display)
72
+ pyvista_surfaces(
73
+ meshes,
74
+ colors=None if not legend else _rank_colors(len(boxes)),
75
+ scalars=None if legend else "rank",
76
+ cmap="turbo",
77
+ opacity=opacity,
78
+ show_edges=True,
79
+ legend=legend,
80
+ plotter=plotter,
81
+ )
82
+ if shape is not None:
83
+ if shape.ndim != 3:
84
+ raise ValueError("shape must be three-dimensional")
85
+ bounds = (np.asarray(lower_bound, float), np.asarray(upper_bound, float))
86
+ shapes = {
87
+ name: obj.to_mesh(
88
+ resolution=resolution,
89
+ bounds=bounds if getattr(obj, "inverted", False) else None,
90
+ )
91
+ for name, obj in _objects(shape).items()
92
+ }
93
+ pyvista_surfaces(
94
+ shapes,
95
+ colors=["0.55"] * len(shapes),
96
+ opacity=shape_opacity,
97
+ legend=False,
98
+ plotter=plotter,
99
+ )
100
+ plotter.show_bounds(
101
+ bounds=np.array([lower_bound, upper_bound], dtype=float).T.ravel(),
102
+ location="outer",
103
+ xtitle="x",
104
+ ytitle="y",
105
+ ztitle="z",
106
+ n_xlabels=3,
107
+ n_ylabels=3,
108
+ n_zlabels=3,
109
+ fmt="%.2g",
110
+ )
111
+ # x to the right, y into the picture and z up, so rank 0 is in front.
112
+ plotter.view_vector((1.0, -1.0, 1.0))
113
+ plotter.reset_camera()
114
+ plotter.camera.zoom(0.9)
115
+ if legend and plotter.legend is not None:
116
+ plotter.legend.SetPosition(0.82, 0.70) # clear of the boxes
117
+ if display or save:
118
+ plotter.show(screenshot=str(save) if save else None, auto_close=False)
119
+ return plotter
120
+
121
+
122
+ def show_decomposition_2d(
123
+ decomposition: Any,
124
+ lower_bound: Any,
125
+ upper_bound: Any,
126
+ num_gridpoints: Any,
127
+ *,
128
+ shape: Any = None,
129
+ ax: Any = None,
130
+ opacity: float = 0.35,
131
+ labels: bool = True,
132
+ display: bool = True,
133
+ save: Any = None,
134
+ ) -> Any:
135
+ """Draw the subdomains of a 2D decomposition with Matplotlib; return the axes."""
136
+ if decomposition.ndim != 2:
137
+ raise ValueError("show needs a two-dimensional decomposition")
138
+ import matplotlib.pyplot as plt
139
+ from matplotlib.patches import Rectangle
140
+
141
+ from simgeometry.visualization import _finish
142
+
143
+ if ax is None:
144
+ ax = plt.figure(layout="constrained").add_subplot()
145
+ boxes = decomposition.subdomain_boxes(lower_bound, upper_bound, num_gridpoints)
146
+ colors = _rank_colors(len(boxes))
147
+ for rank, ((x0, y0), (x1, y1)) in enumerate(boxes):
148
+ ax.add_patch(
149
+ Rectangle(
150
+ (x0, y0),
151
+ x1 - x0,
152
+ y1 - y0,
153
+ facecolor=(*colors[rank], opacity),
154
+ edgecolor=colors[rank],
155
+ linewidth=1.5,
156
+ )
157
+ )
158
+ if labels:
159
+ ax.text(
160
+ (x0 + x1) / 2,
161
+ (y0 + y1) / 2,
162
+ f"rank {rank}",
163
+ ha="center",
164
+ va="center",
165
+ fontsize="small",
166
+ )
167
+ if shape is not None:
168
+ if shape.ndim != 2:
169
+ raise ValueError("shape must be two-dimensional")
170
+ for obj in _objects(shape).values():
171
+ closed = getattr(obj, "closed", True)
172
+ for loop in obj.outlines():
173
+ if closed:
174
+ loop = np.concatenate([loop, loop[:, :1]], axis=1)
175
+ ax.plot(*loop, color="black", linewidth=1.2, solid_capstyle="round")
176
+ ax.set_xlim(lower_bound[0], upper_bound[0])
177
+ ax.set_ylim(lower_bound[1], upper_bound[1])
178
+ ax.set_aspect("equal")
179
+ ax.set_xlabel("x")
180
+ ax.set_ylabel("y")
181
+ return _finish(ax, display, save)