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.
- {simgeometry-0.1.0/src/simgeometry.egg-info → simgeometry-0.2.0}/PKG-INFO +76 -13
- simgeometry-0.2.0/README.md +126 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/pyproject.toml +24 -8
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/__init__.py +26 -1
- simgeometry-0.2.0/src/simgeometry/_decomposition_views.py +181 -0
- simgeometry-0.2.0/src/simgeometry/_level_set.py +198 -0
- simgeometry-0.2.0/src/simgeometry/_profiling.py +8 -0
- simgeometry-0.2.0/src/simgeometry/_rounded_box.py +125 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/api.py +36 -13
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/boundingbox.py +1 -1
- simgeometry-0.2.0/src/simgeometry/collection.py +205 -0
- simgeometry-0.2.0/src/simgeometry/crossings.py +532 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/cut_edges.py +263 -10
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/cylindrical.py +1 -1
- simgeometry-0.2.0/src/simgeometry/domain_decomposition.py +913 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/evaluation.py +39 -1
- simgeometry-0.2.0/src/simgeometry/fractions.py +544 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/grid.py +19 -6
- simgeometry-0.2.0/src/simgeometry/kernels/__init__.py +23 -0
- simgeometry-0.2.0/src/simgeometry/kernels/absorb_polygons_2d/__init__.py +16 -0
- simgeometry-0.2.0/src/simgeometry/kernels/absorb_polygons_2d/absorb_polygons_2d_cuda.cu +63 -0
- simgeometry-0.2.0/src/simgeometry/kernels/absorb_polygons_2d/absorb_polygons_2d_numpy.py +65 -0
- simgeometry-0.2.0/src/simgeometry/kernels/absorb_polygons_2d/absorb_polygons_2d_pyccel.py +152 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_ball/__init__.py +2 -4
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_ball/contains_ball_metal.py +3 -3
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_box/__init__.py +2 -4
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_box/contains_box_metal.py +3 -3
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_polygon_2d/__init__.py +2 -4
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_polygon_2d/contains_polygon_2d_metal.py +3 -3
- simgeometry-0.2.0/src/simgeometry/kernels/polygon_hit.cuh +56 -0
- simgeometry-0.2.0/src/simgeometry/kernels/polygon_hits.py +139 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/mesh.py +43 -31
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/__init__.py +12 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/aperture2d.py +38 -12
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/aperture3d.py +15 -1
- simgeometry-0.2.0/src/simgeometry/objects/axisymmetric.py +239 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/circle.py +47 -4
- simgeometry-0.2.0/src/simgeometry/objects/cone.py +143 -0
- simgeometry-0.2.0/src/simgeometry/objects/cuboid.py +275 -0
- simgeometry-0.2.0/src/simgeometry/objects/cylinder.py +134 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/function_object.py +11 -8
- simgeometry-0.2.0/src/simgeometry/objects/helix.py +335 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/line.py +40 -3
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/polygon.py +51 -3
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/polyline.py +2 -2
- simgeometry-0.2.0/src/simgeometry/objects/rectangle.py +263 -0
- simgeometry-0.2.0/src/simgeometry/objects/slanted_tube.py +379 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/sphere.py +40 -3
- simgeometry-0.2.0/src/simgeometry/objects/torus.py +125 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/objects/triangle.py +57 -3
- simgeometry-0.2.0/src/simgeometry/objects/tube.py +167 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/operations.py +344 -34
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/panels.py +16 -5
- simgeometry-0.2.0/src/simgeometry/polygon_objects.py +244 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/queries.py +272 -114
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/sampling.py +4 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/shapes.py +111 -16
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/simulation_domain.py +17 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/visualization.py +418 -167
- {simgeometry-0.1.0 → simgeometry-0.2.0/src/simgeometry.egg-info}/PKG-INFO +76 -13
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry.egg-info/SOURCES.txt +20 -5
- simgeometry-0.2.0/src/simgeometry.egg-info/entry_points.txt +2 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry.egg-info/requires.txt +7 -2
- simgeometry-0.1.0/LICENSE.txt +0 -21
- simgeometry-0.1.0/README.md +0 -65
- simgeometry-0.1.0/src/simgeometry/collection.py +0 -65
- simgeometry-0.1.0/src/simgeometry/crossings.py +0 -319
- simgeometry-0.1.0/src/simgeometry/domain_decomposition.py +0 -496
- simgeometry-0.1.0/src/simgeometry/fractions.py +0 -68
- simgeometry-0.1.0/src/simgeometry/kernels/__init__.py +0 -1
- simgeometry-0.1.0/src/simgeometry/kernels/_build.py +0 -53
- simgeometry-0.1.0/src/simgeometry/kernels/_metal.py +0 -41
- simgeometry-0.1.0/src/simgeometry/kernels/compile.py +0 -24
- simgeometry-0.1.0/src/simgeometry/objects/cuboid.py +0 -173
- simgeometry-0.1.0/src/simgeometry/objects/rectangle.py +0 -130
- {simgeometry-0.1.0 → simgeometry-0.2.0}/setup.cfg +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/_bounds.py +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/_normalize.py +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/_particle_boundaries.py +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/_types.py +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_ball/contains_ball_cuda.cu +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_ball/contains_ball_numpy.py +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_ball/contains_ball_pyccel.py +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_box/contains_box_cuda.cu +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_box/contains_box_numpy.py +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_box/contains_box_pyccel.py +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_polygon_2d/contains_polygon_2d_cuda.cu +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_polygon_2d/contains_polygon_2d_numpy.py +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/kernels/contains_polygon_2d/contains_polygon_2d_pyccel.py +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/plotting.py +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/py.typed +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry/utils.py +0 -0
- {simgeometry-0.1.0 → simgeometry-0.2.0}/src/simgeometry.egg-info/dependency_links.txt +0 -0
- {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.
|
|
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
|
-
|
|
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,
|
|
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
|
|
81
|
-
Matplotlib is optional. Install `simgeometry[plot]` for
|
|
82
|
-
labelled evaluation and surface meshing, or
|
|
83
|
-
for interactive 3D clipping with PyVista.
|
|
84
|
-
MPI, `simgeometry[pyccel]` for compiled
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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)
|