Skip to content

Unify the GMSH readers, repair the triangle path, and keep test artifacts out of the repo - #122

Merged
m-reuter merged 5 commits into
Deep-MI:mainfrom
m-reuter:io-gmsh
Sep 22, 2026
Merged

m-reuter merged 5 commits into
Deep-MI:mainfrom
m-reuter:io-gmsh

Conversation

@m-reuter

@m-reuter m-reuter commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Tail of #121, which was merged before the last three commits reached the
remote, plus the GMSH work that followed from reviewing them.

The GMSH triangle reader was unreachable and broken

_tria_io.read_gmsh had no caller anywhere and returned a raw 5-tuple rather
than a mesh. Its binary branch could never have run: the file is opened in text
mode, so struct.unpack("i", f.read(4)) fails on a str, well before the
np.fromstring that #121 replaced. That branch is removed rather than
repaired, roughly 70 lines, and now raises [binary format not implemented],
the message _tet_io already used. Real binary support needs the file reopened
in binary mode with the header lines decoded manually, and there is no .msh
fixture to validate it against.

Six assert statements did the format validation. Asserts vanish under
python -O, so a truncated file silently produced a wrong mesh; they are now
ValueError in the same style _tet_io already used. The file handle also
leaked on every error path, so the body moved into a context manager.

One parser for both mesh types

The two readers had drifted into different capabilities for the same format,
and the weaker one was the one people actually use. Parsing now lives once in
lapy/_gmsh_io.py, with TriaMesh.read_gmsh and TetMesh.read_gmsh as thin
wrappers over it. _tria_io.py loses 176 lines, _tet_io.py loses 83.

TetMesh.read_gmsh gains two fixes from the merge: a $PhysicalNames block
used to make it fail outright, and its uniform-width assumption meant any
surface triangles gmsh emitted alongside the tetrahedra killed the read. Both
work now, and one file holding both types serves either reader. Node dtype
stays float32, matching every other reader in the package.

Fail early when the file cannot supply what you asked for

Reading a large volume mesh only to discover it has no triangles is wasteful,
so read_gmsh(filename, want=...) rejects such a file as early as it can:

  • node coordinates are skipped rather than parsed until the element types are
    known, since skipping costs 14 ms where parsing costs 464 ms on a 200k-node
    file, and the offset is kept so they are read only if the file is usable;
  • the type check is a text scan of the element block rather than an integer
    conversion, and only runs when the first record is not already the wanted
    type, so an ordinary triangle mesh never pays for it.

On a 62 MB mesh with 1.2M tetrahedra and no triangles: a full read as TetMesh
takes 2188 ms, rejection as TriaMesh takes 493 ms.

Element parsing is vectorized

The block is read in one islice and converted in one np.array(...split()).
Records share a width only when the block holds one element type with one tag
count, which reshapes in a single step; mixed blocks fall back to walking the
records. On 1M elements: 0.98 s to 0.52 s uniform, 1.00 s to 0.81 s mixed,
identical output in both cases.

Sections we do not use, $PhysicalNames, $Periodic, $NodeData, are now
skipped rather than rejected, so real gmsh files load that previously did not.

Test artifacts out of the repo

test_visualization_meshes.py wrote data/cubeTria.ev and data/cubeTetra.ev
on every run, with values that differ each time because fem.eigs(k=3) takes
no v0 or rng. Nothing read them back and the tests only assert the file
exists. They now write to tmp_path, the two files are untracked, and *.ev
is ignored alongside .agentbridge/, .agent-work/ and scratch/. .venv
was widened to .venv* so side-by-side interpreter envs stay untracked.

Verification

Ruff clean. 134 passed, 1 skipped on Python 3.13.9 with numpy 2.5.3 and on
3.11.9 with numpy 2.4.0rc1. The skip is test_cholmod_matches_lu, which needs
scikit-sparse and is waiting on #119.

Five new tests in lapy/utils/tests/test_gmsh_io.py cover the round trip, one
file serving both readers, rejection of the wrong cell type, skipped sections,
and binary rejection. They write their own .msh into tmp_path, so no
fixture is added.

Behaviour changes

TetMesh.read_gmsh accepts files it previously rejected, as described above.
TriaMesh.read_gmsh is new. Both now require a .msh extension, which was
already the tet reader's rule.

@codecov

codecov Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.13043% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.64%. Comparing base (79e3b7a) to head (5b119f9).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
lapy/_gmsh_io.py 76.69% 12 Missing and 12 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #122      +/-   ##
==========================================
+ Coverage   55.93%   60.64%   +4.70%     
==========================================
  Files          15       16       +1     
  Lines        3184     3087      -97     
  Branches      418      406      -12     
==========================================
+ Hits         1781     1872      +91     
+ Misses       1246     1046     -200     
- Partials      157      169      +12     
Flag Coverage Δ
unittests 60.64% <79.13%> (+4.70%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@m-reuter m-reuter changed the title Repair the GMSH triangle reader and keep test artifacts out of the repo Unify the GMSH readers, repair the triangle path, and keep test artifacts out of the repo Sep 22, 2026
@m-reuter
m-reuter merged commit eb0e157 into Deep-MI:main Sep 22, 2026
29 checks passed
@m-reuter
m-reuter deleted the io-gmsh branch September 22, 2026 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant