Skip to content

LaPy v1.7.0

Latest

Choose a tag to compare

@m-reuter m-reuter released this 11 Oct 07:53
· 14 commits to main since this release
14cf4b4

This release adds new tools for triangle mesh topology and quality: vertex-level manifold checks, genus and orientability, cutting meshes open, self-intersection tests and isotropic remeshing. It also fixes several long-standing bugs in the conformal mapping and the GMSH readers, keeps LaPy working with NumPy 2.5, drops Python 3.10 and adds Python 3.14.

New features

Mesh topology (#123)

  • TriaMesh.is_manifold() now also checks vertices: every vertex needs a single fan of triangles. Pinch vertices, where several cones of triangles touch only at the vertex (two tetrahedra sharing a corner, a bow tie, a figure-8 boundary), used to pass.
  • TriaMesh.pinch_vertices() lists them, and TriaMesh.split_pinch_vertices_() gives every further fan its own copy of the vertex.
  • TriaMesh.cut_(edges) cuts a mesh open along vertex pairs: a closed loop becomes two boundary loops, an open path one loop around it.
  • TriaMesh.genus() returns the genus of closed and open surfaces with any number of components.
  • TriaMesh.is_orientable() tells whether a mesh can be oriented consistently, independent of its current orientation (a Moebius band or a Klein bottle cannot).

Self-intersection tests (#125)

New module lapy.intersect:

  • triangles_intersect(a, b) tests pairs of triangles (Moeller 1997).
  • self_intersections(tria, subset=None) finds intersecting triangle pairs, optionally only those involving a subset of triangles, which is much faster after a local change.
  • undo_intersections(tria, v_new) moves vertices back where new positions, for example after smoothing, make the surface intersect itself.

Isotropic remeshing (#126)

New module lapy.remesh:

  • remesh(tria, target_length=None) remeshes a surface to near-equilateral triangles after Botsch and Kobbelt (2004). Edge collapses keep the topology (link condition), new vertices stay on the input surface, boundary vertices stay in place and 2D meshes stay 2D.
  • closest_points(tria, points) finds the closest point on a mesh for each query point.

Both are fully vectorized with NumPy and SciPy and need no new dependency.

CHOLMOD through cholespy

use_cholmod=True now uses cholespy instead of scikit-sparse. Install it with pip install lapy[chol]. The chol extra used to resolve to scikit-sparse 0.5.0, which broke use_cholmod=True. A Solver with dtype=np.float32 works with it and returns float32 results, and a matrix that is not positive definite is reported as a clear ValueError. Thanks @izachp for the proposal and benchmarks (#119, #127).

Other additions

  • Solver.poisson has a new integrate argument to pass a right hand side that is already integrated, without building a new solver (#115, thanks @magnesium2400).
  • lapy.conformal.spherical_tutte_map, which spherical_conformal_map now falls back to when the harmonic map degenerates (#120).
  • TriaMesh.read_gmsh works and returns a TriaMesh. TriaMesh.read_gmsh and TetMesh.read_gmsh share one parser that rejects a file early when it holds no cells of the requested type (#121, #122).

Behaviour changes

  • Meshes with pinch vertices now fail is_manifold(), and boundary_loops() raises an error on figure-8 boundaries instead of returning a possibly wrong result. Call split_pinch_vertices_() first to keep the old behaviour.
  • genus() raises for non-manifold and non-orientable meshes.
  • The CHOLMOD backend needs cholespy instead of scikit-sparse.
  • spherical_conformal_map returned a mirrored, anti-conformal sphere. The output is now correctly oriented, so results differ from earlier versions (#120).
  • In-place operations that rebuild the mesh (rm_free_vertices_, keep_largest_connected_component_, orient_, refine_, split_pinch_vertices_, cut_) keep 2D meshes 2D instead of returning 3D vertices with a zero third coordinate.
  • Malformed GMSH files raise ValueError instead of failing an assert. Binary GMSH triangle files raise "binary format not implemented", as tetrahedron files already did; that code path never worked.

Bug fixes

  • TriaMesh.euler() counted three triangles for meshes with one or two triangles.
  • spherical_conformal_map (#120):
    • pinning vertices broke the symmetry of the system matrix, so use_cholmod=True and splu gave different results;
    • the south pole chart was built from a denominator computed before the rescale;
    • a healthy fine mesh could be rejected as degenerate;
    • the systems are now solved in real instead of complex arithmetic.
  • beltrami_coefficient rejects degenerate triangles and computes planar areas without the removed 2D np.cross (#120).
  • Mesh readers (#121):
    • replace the in-place .shape assignment deprecated in NumPy 2.5;
    • replace the binary np.fromstring, which fails on any NumPy 2.
  • TetMesh.read_gmsh handles a $PhysicalNames block and files that hold triangles next to the tetrahedra (#122).
  • GMSH readers close the file on every error path (#122).

Packaging, CI and documentation (#124)

  • Python 3.11 to 3.14 are supported; Python 3.10 is dropped.
  • All runtime dependencies have a minimum version (numpy>=1.23.2, scipy>=1.9.2, nibabel>=4.0, plotly>=5.0, psutil>=5.9), and a new CI job tests exactly these minimums.
  • Removed:
    • setup.py; packaging is configured in pyproject.toml;
    • the full extra (use all);
    • numpy from the build requirements.
  • The CI matrix is smaller:
    • pytest runs the oldest and newest Python on every OS, and the versions in between on Linux;
    • the package is built once and checked with twine check;
    • pytest also runs when only pyproject.toml changes.
  • Docstrings are checked with ruff instead of the unmaintained pydocstyle.
  • The changelog page and the unused sphinx-gallery setup are removed from the documentation. Release notes are now kept on GitHub.
  • Deprecation warnings raised inside LaPy now fail the tests (#121).
  • GitHub Actions updated: checkout, setup-python and codecov (#114, #116, #117, #118).

Contributors

@m-reuter, @magnesium2400, @izachp

Full changelog: v1.6.0...v1.7.0