Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
74debaa
Read __version__ from the package metadata (#15)
max-models Oct 20, 2026
657cb48
Test PRs to devel against the max-models devel branches and PRs to ma…
max-models Oct 21, 2026
0ab63bc
Opt-in scope-profiler regions, off by default (#17)
max-models Oct 22, 2026
d7b8eb8
Level sets, axisymmetric solids, conductor voltages, interactive 3D v…
max-models Oct 22, 2026
fa75925
Add interactive 3D views to six tutorials (#19)
max-models Oct 22, 2026
cf51a0b
Add crossing points and 3D views of cut edges (#20)
max-models Oct 22, 2026
7d94403
Preview a domain decomposition without a communicator (#23)
max-models Oct 22, 2026
e5e0560
Add Section.outlines and Section.show_3d (#21)
max-models Oct 22, 2026
be397c3
Add show_3d(points=...) for labelled point sets (#22)
max-models Oct 22, 2026
9598f19
Rounded rectangles and cuboids (fillet) (#24)
max-models Oct 22, 2026
53ed9af
SlantedTube: a tube with a slanted end (#27)
max-models Oct 23, 2026
b5ba8cd
Yee-grid cut-cell fractions (#26)
max-models Oct 23, 2026
2158c56
Swept polygon absorption kernel, moved from picnic (#25)
max-models Oct 23, 2026
46aa49d
Type periodic as one flag or one per axis (#30)
max-models Oct 26, 2026
798fa24
Bump ruff from 0.17.1 to 0.17.2 in the python group (#29)
dependabot[bot] Oct 26, 2026
36fa30f
Bump the docs group in /docs with 2 updates (#28)
dependabot[bot] Oct 27, 2026
79342a8
Use ruff for formatting and import sorting, with the shared pre-commi…
max-models Oct 27, 2026
bf97439
Build the kernels with cunumpy's KernelPackage (#32)
max-models Oct 27, 2026
f39424e
Bump starlight-theme to 0.5.2 and use the newest Node release in CI. …
max-models Oct 28, 2026
bcbec89
Declare the scope_profiler.opt_in_tags entry point. (#34)
max-models Oct 28, 2026
adc5a1d
Bump starlight-theme to 0.6.0 and drop the separate tutorial job (#35)
max-models Oct 29, 2026
b798363
Shorten the titles of the docs pages. (#36)
max-models Oct 29, 2026
65238ce
Add an animated figure to the docs home page. (#37)
max-models Oct 29, 2026
6ed898f
Remove the license. (#39)
max-models Oct 29, 2026
2c16dbb
Merge Tutorials and Full examples into one Examples section. (#38)
max-models Oct 30, 2026
c70ec66
Build the docs with the theme's major tag v0. (#40)
max-models Oct 30, 2026
4f5fbfe
Bump plasma-plots in the lock. (#41)
max-models Oct 30, 2026
fe1cd1f
Find the first crossing of a GeometryCollection per object, with the …
max-models Oct 31, 2026
52fde7f
Bump the docs group in /docs with 2 updates (#43)
dependabot[bot] Nov 2, 2026
9c70309
Bump the python group with 4 updates (#44)
dependabot[bot] Nov 2, 2026
d1a48da
Add method cards and a Numerics section to the docs. (#45)
max-models Nov 3, 2026
66fbb95
Add Helix, a helical wire, and PolarRepeat, copies of a shape about a…
max-models Nov 3, 2026
3f2609e
Add an example page for each object. (#47)
max-models Nov 3, 2026
5b9aa82
Release 0.2.0: Helix, PolarRepeat, method cards, section outlines, le…
max-models Nov 7, 2026
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
34 changes: 25 additions & 9 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,33 @@ concurrency:

jobs:
docs:
uses: max-models/starlight-theme/.github/workflows/docs.yml@v0.4.0
uses: max-models/starlight-theme/.github/workflows/docs.yml@v0
with:
node-version: "24"
# the PyVista scenes in the examples render under a virtual display
xvfb: true
smoke-paths: |
getting-started/installation/index.html
tutorials/index.html
tutorials/grids-and-coordinates/index.html
tutorials/shapes-and-node-masks/index.html
tutorials/parallel-grids/index.html
examples/index.html
examples/grids-and-coordinates/index.html
examples/shapes-and-node-masks/index.html
examples/parallel-grids/index.html
api/simgeometry/index.html
api/simgeometry/grid/index.html
tutorials/grids-and-coordinates/grids_and_coordinates.ipynb
tutorials/shapes-and-node-masks/shapes_and_node_masks.ipynb
tutorials/parallel-grids/parallel_grids.ipynb
tutorials/shapes-and-node-masks/1.png
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
examples/shapes-and-node-masks/1.png
examples/inspecting-geometry/4.html
examples/conductors-and-level-sets/2.html
examples/shapes-and-node-masks/2.png
examples/shapes-and-node-masks/3.html
examples/building-composite-shapes/5.html
examples/slices-and-coordinate-sampling/4.html
examples/sampling-export-and-diagnostics/2.html
examples/grid-intersections-and-cut-edges/2.html
examples/conductors-and-level-sets/4.html
examples/parallel-grids/1.html
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ concurrency:
permissions:
contents: read

env:
# The release (a push to main) builds against PyPI versions only, never [tool.uv.sources].
UV_NO_SOURCES: true

jobs:
release:
runs-on: ubuntu-latest
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/static_analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ on:
- main
- devel

env:
# A PR to main or a push to main (the release) uses PyPI versions only; devel uses the
# devel branches from [tool.uv.sources].
UV_NO_SOURCES: ${{ (github.base_ref || github.ref_name) == 'main' }}

jobs:
ruff:
runs-on: ubuntu-latest
Expand All @@ -26,6 +31,9 @@ jobs:
- name: Check formatting
run: uvx ruff format --check .

- name: Check statement order
run: uvx ssort --check --diff .

pyright:
runs-on: ubuntu-latest
steps:
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/test_pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ on:
- main
- devel

env:
# A PR to main or a push to main (the release) uses PyPI versions only; devel uses the
# devel branches from [tool.uv.sources].
UV_NO_SOURCES: ${{ (github.base_ref || github.ref_name) == 'main' }}

jobs:
pytest:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -41,3 +46,25 @@ jobs:
files: coverage.xml
flags: python-${{ matrix.python-version }}
fail_ci_if_error: false

# PyPI rejects a release whose metadata has a direct reference (name @ git+...), so a pull
# request to main fails here instead of the release after the merge.
no-direct-references:
if: (github.base_ref || github.ref_name) == 'main'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Set up uv
uses: astral-sh/setup-uv@v7
with:
python-version: "3.12"

- name: Check that the wheel has no direct references (name @ git+...)
run: |
uv build --wheel -o dist
if unzip -p dist/*.whl '*.dist-info/METADATA' | grep -E '^Requires-Dist: [^;]*@'; then
echo "::error::Requires-Dist has a direct reference; use a version range and [tool.uv.sources]"
exit 1
fi
46 changes: 0 additions & 46 deletions .github/workflows/test_tutorials.yml

This file was deleted.

8 changes: 7 additions & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ cache:
paths:
- .cache/pip

# PyPI versions only when targeting main; otherwise the devel branches from [tool.uv.sources]
.uv-no-sources: &uv-no-sources
- export UV_NO_SOURCES=$([ "${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-$CI_COMMIT_BRANCH}" = main ] && echo true || echo false)

before_script:
- *uv-no-sources
- python --version ; pip --version # For debugging
- pip install uv
- uv sync --extra dev
Expand All @@ -39,7 +44,8 @@ build:

pages:
image: node:22
before_script: []
before_script:
- *uv-no-sources
script:
- apt-get update && apt-get install -y python3 python3-pip python3-venv
- pip install uv && uv sync --extra docs
Expand Down
11 changes: 8 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@ repos:
rev: v6.0.0
hooks:
- id: check-yaml
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-added-large-files # Prevent giant files from being committed.
args: [--maxkb=500]
exclude: ^(uv\.lock|docs/package-lock\.json)$ # generated lock files
- id: check-merge-conflict # Check for files that contain merge conflict strings.
- repo: https://github.com/kynan/nbstripout
rev: 0.9.0
rev: 0.9.1
hooks:
- id: nbstripout # strip jupyter notebook output
- repo: https://github.com/bwhmather/ssort
rev: 0.17.0
hooks:
- id: ssort # order statements so names are defined before they are used
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.1
rev: v0.17.3
hooks:
- id: ruff
- id: ruff-check # lint, including import sorting (the I rules)
args: [--fix]
- id: ruff-format
128 changes: 128 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,98 @@ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

## [0.2.0] - 2026-11-07

### Added

- One example page per object of `simgeometry.objects`, from `Line` to `Helix`
(`tutorials/objects/`, the Objects group of the Examples): how to build it, membership,
level set and a figure.
- `Helix(start, radius, pitch, turns, wire_radius, axis=..., left_handed=...)`: a helical
wire of round cross-section with hemispherical ends, e.g. the winding of an RF coil. Its
level set is the distance from the centreline (Newton steps along it from the
linearised minima of the nearest turns), exact within a wire radius of the wire and a
bound farther away; `centreline(t)`, `wire_length`, volume and surface area.
- `PolarRepeat(source, count, axis=..., origin=...)`: ``count`` copies of a 3D shape about
an axis, evaluated by folding the point's angle into the first sector, so its cost does
not grow with ``count`` (the slits of a Faraday shield: ``tube - PolarRepeat(slit, 24)``;
the cut cells of 24 slits as fast as of one).
- 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
edges, exactly, with the object each marker hit first. The kernel
`simgeometry.kernels.absorb_polygons_2d` (Pyccel, NumPy, CUDA) and the shared hit test
(`polygon_hit.cuh`, `polygon_hits.py`) moved here from picnic, so picnic and tree-d-3d use
one implementation.
- `shape.yee_fractions(grid)` (`simgeometry.fractions.yee_fractions`): the cut-cell
geometry of a Yee grid in 2D and 3D, for conformal (Dey–Mittra-type) field solvers: the
fraction of every edge's length and every face's area inside the shape, the length of the
surface's trace in every face and that length weighted by the sine between the surface
and the face. Exact for planes; for curved surfaces second order (a circle's area and
perimeter, a sphere's area from the traces). Edges use all crossings, so slits narrower
than a cell are kept; walls on grid lines count like a staircase. Works for every shape
`crossings` supports, sampled where it is not analytic.
- `SlantedTube(start, end, inner_radius, outer_radius, cut_normal)`: a tube (a rod with
`inner_radius=0`) along any axis whose end at `end` is cut by the plane through `end`
with the outward normal `cut_normal`, such as an oven nozzle cut at 45°. Membership,
bounds, volume and surface area are exact; the level set is the larger of the distances
to the uncut tube and to the cut plane (exact on the faces, a bound near the cut's rim);
`crossings` are analytic (both walls, the flat end and the cut), so particles in a thin
duct find every wall. Meshes, `show_3d`, node masks and conductors work as for the other
solids. The tutorial "Shapes and node masks" shows one.

- `Rectangle(..., fillet=r)`: a rectangle with rounded corners, up to a stadium at half
the shorter side; `Cuboid(..., fillet=r)` rounds the edges and corners of a box, like
the `fillet` of the solids of revolution. Both keep the exact level set (the rounded
box distance), analytic `crossings`, segment intersections, closest points and
normals; the outline is straight edges and arcs, refined by `max_spacing`. The
tutorial "Shapes and node masks" shows them.
- `show_3d(points=...)` overlays labelled point sets, e.g. samples, grid nodes or
crossings: an array in the `(3, n)` layout (or `(n, 3)`), or a dict of label to array or
to `{"points", "vectors", "color", "size"}`, with optional arrows such as normals. The
PyVista backend draws them with plasma-plots' `pyvista_points` in the same legend as the
objects (needs `plasma-plots>=0.2.5`); Matplotlib uses `scatter` and `quiver`. The
tutorials "Shapes and node masks" and "Sampling, export and diagnostics" use it.
- `Section.outlines(max_spacing, resolution=, bounds=, world=)`: the sampled boundary loops of a
plane section in plane coordinates (holes clockwise), or on the plane in 3D with
`world=True`; `Section.show_3d()` draws the sliced shape, the cutting plane and the outline in
3D (Matplotlib or PyVista). The tutorial "Slices and coordinate sampling" uses it.
- `DomainDecomposition.preview(size, decompose=..., periodic=..., rank=...)` (or
`proc_sizes=`): the decomposition a `size`-rank run would use, without a communicator, for
inspection and planning; `subdomain_boxes(...)` returns every rank's subdomain corners,
`show_3d(...)` draws them as boxes coloured by rank (plasma-plots, optional `shape=`) and
`show(...)` as rectangles in 2D. The tutorial "Parallel grids" shows an 8-rank scene.
- `bracketed_edge_geometry(...).points` and `.normals`: the crossings in world coordinates
and the outward normals there, `(ndim, n)` like `cut_edges()`; `crossing_owners` indexes
each crossing's object. `BracketedEdgeGeometry` now keeps `spacing`, `periodic` and
`objects`, and `CutEdges` its `geometry`.
- `CutEdges.show_3d()` and `BracketedEdgeGeometry.show_3d()`: the translucent surface, the
crossings and, optionally, the outward normals and solid nodes in 3D (PyVista or
Matplotlib). The tutorials "Grid intersections and cut edges" and "Conductors and level
sets" use them.
- The PyVista backend of `show_3d` draws the surfaces with plasma-plots' `pyvista_surfaces`
(plasma-plots is now part of the `interactive` extra); `color_by="voltage"` colours a
collection's conductors by their voltage at time `t`.
- `level_set_field()`: the level set on a grid in the layout of plasma-plots' 3D views, for
`field.plasma.plot.isosurface(...)`; `evaluate(quantity="level_set")`.
- Interactive 3D scenes in the tutorials "Inspecting geometry" and "Conductors and level
sets" (starlight-theme v0.5.0, `plasma_plots.scene`).
- More interactive 3D scenes in the tutorials: the new solids and a 3D node mask, 3D
transforms and Booleans, an oblique slice through a shell, interior and surface samples,
3D cut edges with normals, the level set of a slotted electrode and its grid-edge crossings.
- `DomainDecomposition.owner_ranks(positions, lower_bound, upper_bound, num_gridpoints,
layout)` and `owned_mask(...)`: vectorised position-to-rank lookup for NumPy and CuPy arrays
(`(ndim, n)` by default, `layout="particle_major"` for `(n, ndim)`).
Expand All @@ -32,9 +122,34 @@ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- `Aperture2D.slit(...)`: two rectangular plates. `exact_outline` and `closed` flags on 2D
shapes.
- The tutorial covers holes, polylines, panels and crossings.
- scope-profiler regions for the expensive operations (`simgeometry.node_mask`,
`simgeometry.crossings`, `simgeometry.to_mesh`, ...), off unless the application enables
the `simgeometry` tag (`sp.setup(enable_tags=["simgeometry"])`). scope-profiler is now a
runtime dependency.
- `shape.level_set(points)`: an analytic level set, negative inside and positive outside like
`signed_distance`, with `has_level_set` and `level_set_exact` (the Euclidean signed
distance). Exact for lines, circles, spheres, rectangles, cuboids, polygons, triangles,
apertures and the new solids; Booleans combine level sets by min/max, transforms scale
them by their smallest stretch (exact for rigid motions and uniform scaling), sections and
collections pass them on. Function objects and `Aperture3D` have none.
- `Cylinder`, `Tube` (hollow cylinder), `Cone` (frustum, or cone with one radius 0) and
`Torus`, along any axis, with exact level sets, volumes, surface areas and tight bounding
boxes; `fillet=` rounds the edges of cylinders and tubes.
- Conductors on `GeometryCollection`: `voltages={name: value or function of t}`,
`with_voltages(...)` (same geometry, other voltages), `voltages_at(t)`, `conductors`, and
`dirichlet_values(grid or points, t=...)` with the label and voltage of each node
(`DirichletValues`; NaN outside conductors).
- Tutorial "Conductors and level sets": an Einzel lens of three ring electrodes with voltages.
- `benchmarks/level_set_crossings.py`: edge crossings on level sets against bisection.

### Changed

- Requires cunumpy 0.7.1 or newer, for its `KernelPackage`.
- The kernels are built with cunumpy's `KernelPackage`
(`simgeometry.kernels.KERNEL_PACKAGE`): `kernels/_build.py`, `kernels/_metal.py` and
`kernels/compile.py` are gone, and the build cache is still `SIMGEOMETRY_KERNEL_CACHE`.
- `__version__` is read from the installed package metadata (`importlib.metadata`), not hardcoded,
so it can no longer disagree with `pyproject.toml` (the 0.1.1 wheel reported 0.1.0).
- One out-of-range rule for owners: per axis, positions below the lower bound belong to the
first block and positions at or above the upper edge to the last (the block index is clipped).
`owner_rank_for_position` now calls `owner_ranks`; positions below the lower bound used to go
Expand All @@ -44,6 +159,16 @@ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
for `axis=0`.
- 2D shapes have no `get_vertices()` any more; use `outlines()`, which returns `(2, n)`
arrays. `PointList` and `Point` are gone.
- `intersection_fractions` (and so `bracketed_edge_geometry`) refines crossings of shapes with
a level set by linear interpolation and Illinois steps on it, about 8 evaluations per edge
instead of 50 (2.5 to 3.3 times faster on NumPy, equally accurate); edges without a sign
change of the level set are bisected as before. A level-set crossing is the root itself, not
the end of the last bisection interval inside the object.
- The sampled paths of `cut_edges`/`intersect_segment` and `crossings` refine on the level set
when there is one; `crossings` then takes its normal from the level-set gradient, as does
`surface_normal`. `signed_distance` returns an exact level set directly, and `to_mesh`
contours the level set of any shape that has one (it did so for balls and boxes only).
- The `FunctionObject3D` example is a rounded cube; use `Torus` for a torus.

### Removed

Expand All @@ -52,6 +177,9 @@ 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).
- `Aperture2D` documents its `axis` as `0` or `1` and its vertices as `(axial, radial)`.
Expand Down
21 changes: 0 additions & 21 deletions LICENSE.txt

This file was deleted.

Loading
Loading