Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ jobs:
examples/parallel-grids/index.html
api/simgeometry/index.html
api/simgeometry/grid/index.html
numerics/index.html
numerics/crossings/index.html
numerics/objects-rectangle/index.html
examples/grids-and-coordinates/grids_and_coordinates.ipynb
examples/shapes-and-node-masks/shapes_and_node_masks.ipynb
examples/parallel-grids/parallel_grids.ipynb
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added

- A Numerics section in the docs (header and sidebar): an overview of the geometry
(`docs/src/content/docs/numerics/index.mdx`, with the main cards) and one page per module,
from the shapes to the grids (starlight-theme's `methodcard: { pages: true }`).
- Method cards (methodcard) on the geometry: the inside tests and level sets of every shape
(line, circle, sphere, boxes with fillets, polygons by the even-odd rule, triangles,
apertures, the solids of revolution and the slanted tube), the Boolean, affine and section
level sets, collections, outlines and boundary panels, the first crossings (analytic and
sampled, Illinois), the swept polygon hit test and absorption, the edge crossings, cut edges
and cut-cell fractions, the grid, the domain decomposition and the particle boundaries,
with their assumptions and references. `test_method_cards.py` checks them: SymPy rules for
19 closed forms, the observed order of `outlines`, `crossings` and
`intersection_fractions`, and the cards of the docs against those of the code. Adds the
`methodcard` dependency.
- `simgeometry.polygon_objects`: `polygon_objects(objects)` packs `Polygon` and
`Aperture2D` objects into flat arrays, and `PolygonObjects.absorb(positions, alive,
start=...)` absorbs the markers that end inside one or whose step crosses one of its
Expand Down Expand Up @@ -149,6 +162,8 @@ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Fixed

- The docstrings render on the API pages: code spans instead of the reST roles (`:func:`,
`:class:`, `:meth:`, ...).
- `Cuboid` keeps its bounds as host floats, so its `crossings` work on CuPy.
- `Polygon.contains_points` with the compiled kernels accepts vertices that are not
C-ordered (a transposed `(2, n)` array).
Expand Down
37 changes: 37 additions & 0 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,46 @@ export default defineDocs({
title: 'simgeometry',
description: 'Shapes, structured grids, and domains for numerical simulations.',
pythonPackage: 'simgeometry',
// the method cards: a Numerics section with the overview (numerics/index.mdx) and one page per
// module, from the shapes to the grids, and a Numerics section on each method's API page
methodcard: {
pages: true,
index: false,
labels: {
'simgeometry.shapes': 'Shape conventions',
'simgeometry.objects.line': 'Line',
'simgeometry.objects.circle': 'Circle',
'simgeometry.objects.sphere': 'Sphere',
'simgeometry.objects.rectangle': 'Rectangle',
'simgeometry.objects.cuboid': 'Cuboid',
'simgeometry.objects.polygon': 'Polygon',
'simgeometry.objects.triangle': 'Triangle',
'simgeometry.objects.aperture2d': 'Aperture2D',
'simgeometry.objects.aperture3d': 'Aperture3D',
'simgeometry.objects.axisymmetric': 'Solids of revolution',
'simgeometry.objects.cylinder': 'Cylinder',
'simgeometry.objects.tube': 'Tube',
'simgeometry.objects.cone': 'Cone',
'simgeometry.objects.torus': 'Torus',
'simgeometry.objects.slanted_tube': 'SlantedTube',
'simgeometry.operations': 'Booleans and transforms',
'simgeometry.collection': 'Collections',
'simgeometry.panels': 'Boundary panels',
'simgeometry.crossings': 'First crossings',
'simgeometry.kernels.polygon_hits': 'Polygon hit test',
'simgeometry.polygon_objects': 'Swept absorption',
'simgeometry.queries': 'Edge crossings',
'simgeometry.cut_edges': 'Cut edges',
'simgeometry.fractions': 'Cut-cell fractions',
'simgeometry.grid': 'Grid',
'simgeometry.domain_decomposition': 'Domain decomposition',
'simgeometry.simulation_domain': 'Particle boundaries',
},
},
nav: [
{ label: 'Getting started', link: '/getting-started/installation/' },
{ label: 'Examples', link: '/examples/' },
{ label: 'Numerics', link: '/numerics/' },
{ label: 'Development', link: '/development/docs/' },
{ label: 'API', link: '/api/simgeometry/' },
],
Expand Down
72 changes: 11 additions & 61 deletions docs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

119 changes: 119 additions & 0 deletions docs/src/content/docs/numerics/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
title: Overview
description: The geometry simgeometry computes, from the shapes and their level sets to the crossings, the cut cells and the decomposition of the grid, and what it leaves out.
sidebar:
label: Overview
---

import MethodCard from '@max-models/starlight-theme/components/MethodCard.astro';

simgeometry is the geometry of the simulation codes, nothing physical: shapes with an inside
test, a signed distance and a normal; the first crossing of a particle's step with them; the
cut-cell data of a grid; and the uniform grids and the MPI process grid that splits them.
Every array query takes component-major points $p$ of shape `(ndim, n)` and runs on NumPy or,
through cunumpy, on CuPy; the hot loops are compiled Pyccel kernels.

Each equation on these pages is declared on the code that implements it, as a method card, and
checked by the tests: 19 closed forms (the level sets of the shapes, the Boolean and affine
level sets, the analytic crossings) against SymPy rules, three discretisations against their
order of convergence. The pages of the section list the cards module by module; this one shows
how they fit together.

## Shapes

A shape is a set $S$ with a membership test (`contains_points`) and, where it has an analytic
description, a level set $\phi$: negative inside, zero on the boundary, the exact Euclidean
signed distance where `level_set_exact` is true and a bound on it otherwise. An inverted shape is
the complement, with the sign of $\phi$ flipped
([shape conventions](/simgeometry/numerics/shapes/)). The level sets are the standard exact
distances of boxes, segments, capped cones and tori:

- [Line](/simgeometry/numerics/objects-line/), [Circle](/simgeometry/numerics/objects-circle/),
[Sphere](/simgeometry/numerics/objects-sphere/);
- boxes with rounded edges: [Rectangle](/simgeometry/numerics/objects-rectangle/),
[Cuboid](/simgeometry/numerics/objects-cuboid/);
- polygons by the even-odd rule: [Polygon](/simgeometry/numerics/objects-polygon/),
[Triangle](/simgeometry/numerics/objects-triangle/),
[Aperture2D](/simgeometry/numerics/objects-aperture2d/), and the profile of
[Aperture3D](/simgeometry/numerics/objects-aperture3d/) (no level set);
- [solids of revolution](/simgeometry/numerics/objects-axisymmetric/), whose distance is that
of their profile in the half-plane $(s, \rho)$:
[Cylinder](/simgeometry/numerics/objects-cylinder/),
[Tube](/simgeometry/numerics/objects-tube/), [Cone](/simgeometry/numerics/objects-cone/),
[Torus](/simgeometry/numerics/objects-torus/), and the
[SlantedTube](/simgeometry/numerics/objects-slanted-tube/), a bound near its cut;
- [Booleans, affine transforms and sections](/simgeometry/numerics/operations/), whose level
sets are bounds, and [collections](/simgeometry/numerics/collection/) of labelled objects.

<MethodCard name="simgeometry.objects.rectangle.Rectangle" headingLevel={3} />

2D shapes also give their boundary as counter-clockwise loops (`outlines`, second order in the
spacing for curved boundaries) and as [boundary panels](/simgeometry/numerics/panels/) for
boundary-element solvers.

## Crossings

A particle pusher tests the whole step $a \to b$ of every marker, not only its end, so a fast
particle cannot pass through a thin plate. [First crossings](/simgeometry/numerics/crossings/)
are analytic for balls, boxes, polygons, slanted tubes and their affine images; other shapes
are sampled along the step and refined by Illinois steps on the level set, or bisected on
membership without one.

<MethodCard name="simgeometry.crossings.crossings" headingLevel={3} />

The compiled [swept absorption](/simgeometry/numerics/polygon-objects/) of 2D markers runs the
same [segment-edge test](/simgeometry/numerics/kernels-polygon-hits/) against packed polygon
objects in one pass and returns the object each marker hit first.

## Cut cells

[Edge crossings](/simgeometry/numerics/queries/) locate where the edges of a grid enter a shape
(Illinois on the level set, bisection otherwise); [cut edges](/simgeometry/numerics/cut-edges/)
collect them per grid edge for embedded-boundary finite differences.
[Cut-cell fractions](/simgeometry/numerics/fractions/) give the inside part of every cell by
midpoint quadrature, and of every edge and face of a Yee grid, with the trace of the surface,
for conformal field solvers.

## Grids and decomposition

A [grid](/simgeometry/numerics/grid/) has uniform nodes per axis, the first and last on the
bounds (one spacing short of the upper bound on a periodic axis). The
[domain decomposition](/simgeometry/numerics/domain-decomposition/) splits it over a process
grid with row-major ranks: one near-even split of the nodes, shared by the field arrays
(mpiarray) and the markers, whose owner follows from the subdomain edges. The
[particle boundaries](/simgeometry/numerics/simulation-domain/) of the box kill, reflect, wrap
or reinject markers per axis.

## Conventions

Lengths are in the units of the caller. Shapes and grids are axis-aligned unless an axis or a
matrix is given. Normals have unit length and point out of the region where `contains` is
true, so they flip for inverted shapes. Fractions along a step lie in $(0, 1]$: a crossing at
the very start ($t \le 10^{-12}$) is not reported, one exactly at the end is. Boundary
conventions of the inside tests differ: `Cuboid`, `Line` and the balls are closed; `Rectangle`,
`Polygon`, `Triangle` and `Aperture3D` use the half-open bounding box $l \le p < u$.

## Not covered

- No winding-number test: self-intersecting polygons follow the even-odd rule.
- The absorption kernel reports the object only, not the hit point, edge or normal, and tests
the end points of long (wrapped) steps only.
- Sampled crossings miss features thinner than a step over `samples`; Boolean, section and
collection level sets are bounds, so `signed_distance` falls back to a sampled mesh for them.
- `Aperture3D` and function objects have no level set and no analytic crossing; a section of a
3D shape only bounds the in-plane distance.
- Halo exchange, ghost cells and field layouts are mpiarray's; simgeometry hands over the
process grid and the halo width.
- The process-grid search does not balance the axes and never fails.
- The cut-cell fractions have no single order: the Yee fractions are exact for planes and second
order or better for curved surfaces, the midpoint fractions are multiples of $n^{-d}$.

## Verification

Besides the checks of the cards, the tests compare every vectorised inside test with the scalar
one, the compiled kernels (Pyccel, NumPy, CUDA in emulation) with each other and with the
shapes, the level sets with exact distances and the Boolean bounds, the analytic crossings with
dense sampling, the Illinois roots with bisection (fewer than 10 level-set evaluations per edge
against 50 membership tests), the Yee fractions with planes (exact) and with a circle and a
sphere (area, perimeter and volume), and the decomposition with brute-force owners and with
runs on 4 and 6 MPI ranks.
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ classifiers = [
dependencies = [
"cunumpy>=0.6.2",
"maybempi>=0.1",
"methodcard",
"mpiarray>=0.1.0",
"numpy",
"scope-profiler>=0.7.5",
Expand Down Expand Up @@ -53,7 +54,7 @@ optional-dependencies.mpi = [ "mpi4py" ]
optional-dependencies.pyccel = [ "pyccel>2.0" ]
optional-dependencies.gpu = [ "cupy-cuda12x" ]
optional-dependencies.metal = [ "mlx; sys_platform == 'darwin' and platform_machine == 'arm64'" ]
optional-dependencies.test = [ "mpi4py", "pytest", "pytest-cov", "simgeometry[clip]" ]
optional-dependencies.test = [ "methodcard[symbolic]", "mpi4py", "pytest", "pytest-cov", "simgeometry[clip]" ]
urls.Source = "https://github.com/max-models/simgeometry"

[project.entry-points."scope_profiler.opt_in_tags"]
Expand Down Expand Up @@ -87,6 +88,7 @@ reportMissingImports = "error"
ini_options.pythonpath = [ "src" ]
ini_options.testpaths = [ "src" ]
ini_options.addopts = "-ra"
ini_options.markers = [ "convergence: an order-of-accuracy test of a method card" ]

[tool.coverage]
run.omit = [ "*/tests/*" ]
Expand All @@ -98,6 +100,7 @@ report.show_missing = true
[tool.uv.sources]
cunumpy = { git = "https://github.com/max-models/cunumpy", branch = "devel" }
maybempi = { git = "https://github.com/max-models/maybempi", branch = "devel" }
methodcard = { git = "https://github.com/max-models/methodcard", branch = "devel" }
mpiarray = { git = "https://github.com/max-models/mpiarray", branch = "devel" }
plasma-plots = { git = "https://github.com/max-models/plasma-plots", branch = "devel" }
scope-profiler = { git = "https://github.com/max-models/scope-profiler", branch = "devel" }
2 changes: 1 addition & 1 deletion src/simgeometry/_decomposition_views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Views of a domain decomposition: one box (3D) or rectangle (2D) per rank.

Used by :meth:`DomainDecomposition.show_3d` and :meth:`DomainDecomposition.show`.
Used by `DomainDecomposition.show_3d` and `DomainDecomposition.show`.
Nothing here communicates; the boxes come from ``subdomain_boxes``.
"""

Expand Down
6 changes: 3 additions & 3 deletions src/simgeometry/_level_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@


def has_level_set(shape) -> bool:
"""Whether ``shape`` provides :meth:`~simgeometry.shapes.Shape.level_set`."""
"""Whether ``shape`` provides `level_set`."""
return bool(getattr(shape, "has_level_set", False))


Expand Down Expand Up @@ -76,7 +76,7 @@ def segment_distance(points, starts, ends):
"""Unsigned distance of the ``(2, n)`` points to the nearest of the segments.

``starts`` and ``ends`` are ``(2, m)``. Points are processed in chunks so that
at most :data:`MAX_PAIRS` point-segment pairs are held at once.
at most `MAX_PAIRS` point-segment pairs are held at once.
"""
n, m = points.shape[1], starts.shape[1]
edge = ends - starts
Expand Down Expand Up @@ -178,7 +178,7 @@ def illinois_roots(
def level_set_gradient(shape, points, spacing: float, array_xp=np):
"""Central-difference gradient ``(ndim, n)`` of ``shape.level_set`` at the points.

``array_xp`` is the array module of ``points``, as in :func:`illinois_roots`.
``array_xp`` is the array module of ``points``, as in `illinois_roots`.
"""

def level(p):
Expand Down
Loading
Loading