Skip to content

Add openCARP backend - #93

Open
finsberg wants to merge 6 commits into
mainfrom
finsberg/opencarp-backend
Open

finsberg wants to merge 6 commits into
mainfrom
finsberg/opencarp-backend

Conversation

@finsberg

@finsberg finsberg commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds an opencarp backend that writes and reads linear meshes in the ASCII mesh format of the cardiac simulator openCARP:

File Content
<base>.pts Node coordinates (µm), z = 0 in 2D
<base>.elem Elements with region tags (from cell tags)
<base>.lon Fiber, or fiber + sheet, direction per element
<base>_<marker>.vtx Nodes of each facet (or vertex) marker
<base>_<marker>.surf Facets of each marker of a 3D mesh, as Tr or Qd faces

io4dolfinx.backends.opencarp.write_opencarp writes the mesh, tags and fibers in one call:

from io4dolfinx.backends.opencarp import write_opencarp

write_opencarp(
    "heart", mesh, scale=1000.0,  # mesh in mm
    cell_tags=cell_tags, facet_tags=facet_tags, fiber=f0, sheet=s0,
    markers={"ENDO": (1, 2), "EPI": (2, 2)},
)

Supported meshes: triangles (Tr) and quadrilaterals (Qd) in 2D, tetrahedra (Tt) and hexahedra (Hx) in 3D, one element type per mesh.

The same files can be written and read through the generic API with backend="opencarp": write_mesh_input_order, write_meshtags, write_cell_data, read_mesh, read_meshtags and read_cell_data.

Design

  • Output does not depend on the number of processes. Nodes and elements use the input ordering (input_global_indices, original_cell_index). Rank 0 gathers and writes everything, and the files are byte-identical on 1–4 ranks.

  • Changes to existing code. Before this PR, cell data could not be passed to a backend in input order. This PR:

    • adds an on_input_mesh option to write_cell_data, using exchange_to_owners;
    • adds the same option to write_point_data, for other backends used with write_mesh_input_order (the openCARP backend does not write point data);
    • adds an on_input_mesh: bool = False field to MeshData, MeshTagsData and ArrayData.

    The backend refuses data without the flag, so a .lon file cannot end up out of step with .elem. The defaults leave the other backends unchanged.

  • Units: openCARP uses µm, so scale (the factor converting mesh coordinates to µm) is required for both writing and reading. There is no default.

  • Node order and orientation follow openCARP's own mesher, checked against its output:

    • 2D elements are counter-clockwise in the xy-plane, and 3D elements have positive volume. Elements with the opposite orientation are mirrored.
    • Qd nodes are counter-clockwise, converted from dolfinx's tensor-product order.
    • Hx lists one face (nodes 0–3), followed by the nodes opposite nodes 0, 3, 2 and 1.
    • .surf faces point out of the mesh and are written in a fixed order.
  • 2D meshes are written with z = 0 and read back with 2D coordinates. Fibers get z = 0 in .lon and are read back with two components. Markers are written as .vtx files only, as the backend writes .surf files only for the faces of 3D meshes. Reading rejects 2D elements that do not lie in a plane of constant z (surfaces in 3D).

  • Fibers are interpolated to DG-0 and normalised. Quadrature-space fields are rejected with a clear error, because interpolating them into DG-0 fails in dolfinx 0.11.

  • .vtx header is extra by default. openCARP 19 warns that intra is wrong for indices into the whole mesh.

  • Out of scope: binary formats, higher-order and mixed-type meshes, surface meshes in 3D, and .igb simulation output.

Testing

tests/test_opencarp.py does not need openCARP. It runs serially and under MPI, for all four element types, and covers:

  • file content: headers, counts, index ranges, unit fibers, orientation and total volume or area, the Qd/Hx node pattern, .vtx nodes on the right faces, outward .surf normals with quadrilateral faces in cyclic order;
  • byte-identical output between a COMM_WORLD write and a COMM_SELF write;
  • write → read round trips of the mesh, region tags, facet and vertex markers, and fiber/sheet;
  • error paths, which must raise on every rank.

tests/test_write_data_input_order.py checks, without any backend, that write_point_data and write_cell_data with on_input_mesh=True give the same node and cell order and per-rank ranges as write_mesh_input_order. It covers tetrahedra and hexahedra and fails if the reordering is removed.

All new tests pass serially and on 2, 3 and 4 ranks. The existing original-checkpoint, meshtags, VTKHDF, XDMF and backend tests also pass.

As a manual check, openCARP 19.0.0 (conda-forge) ran a short monodomain simulation on a mesh of each element type, written on 2 ranks. Each run used the region tags for the ionic model and a .vtx file as the stimulus. All runs finished with no errors or warnings and finite voltages, and activation spread from the stimulatedface to the opposite face. The .surf files were not used by these runs.

Docs

New page docs/backends/opencarp.rst, with entries in _toc.yml and the README.

🤖 Generated with Claude Code

finsberg and others added 5 commits October 27, 2026 20:10
Write and read linear tetrahedral meshes in openCARP's ASCII format:
nodes (.pts), elements with region tags (.elem), fiber and sheet
directions (.lon), and facet or vertex markers (.vtx, .surf).

Everything is written in the input ordering and by rank 0, so the files
do not depend on the number of processes. Tetrahedra are oriented with
positive volume and .surf triangles point out of the mesh. Coordinates
are converted to µm with a required scale factor.

io4dolfinx.backends.opencarp.write_opencarp writes the mesh, tags and
fibers in one call.

To support this, MeshData, MeshTagsData and ArrayData get an
on_input_mesh flag, and write_cell_data gets an on_input_mesh option
that sends cell values to input cell order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Like write_cell_data, write_point_data can now send node values in the
node ordering of the input mesh, matching write_mesh_input_order. Both
use a shared helper that moves values to the process holding their
input index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kend

Write and read Tr and Qd elements for 2D meshes and Hx elements for 3D
meshes, besides Tt. Node orderings and orientations follow openCARP's
mesher: counter-clockwise in 2D, positive volume in 3D, and hexahedra
listing one face followed by the nodes opposite nodes 0, 3, 2 and 1.

2D meshes are written with z = 0 and read back with 2D coordinates and
2D fibers. Their markers are written as .vtx files only. Facet markers
of hexahedral meshes are written as outward Qd faces in .surf files.

Errors raised while reading chunks on some ranks are now raised on all
ranks, so that no rank waits in a later collective call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@finsberg
finsberg requested a review from jorgensd October 30, 2026 09:18
@jorgensd

Copy link
Copy Markdown
Member
from io4dolfinx.backends.opencarp import write_opencarp

write_opencarp(
    "heart", mesh, scale=1000.0,  # mesh in mm
    cell_tags=cell_tags, facet_tags=facet_tags, fiber=f0, sheet=s0,
    markers={"ENDO": (1, 2), "EPI": (2, 2)},
)

I'm not that keen on this new API entry point.
As we discussed, as long as the flag write_on_input_mesh is passed consistently in write_mesh and write_meshtags the outputted mesh should work with opencarp.

Of course having the new write_on_input_mesh flag in write_meshtags is fine, as it just add functionality.

Furthermore, I think this function belongs as a helper function somewhere else, rather than being part of the actual backend, as it is very specific..:P

Remove io4dolfinx.backends.opencarp.write_opencarp. The backend module
documentation now describes how to write a mesh, its tags and its fiber
and sheet directions with write_mesh_input_order (or write_mesh),
write_meshtags and write_cell_data.

The backend accepts data in the DOLFINx ordering as well as the input
ordering, as long as every step uses the same one. Only the input
ordering gives files that do not depend on the number of processes.
The on_input_mesh fields of MeshData, MeshTagsData and ArrayData are
no longer needed and are removed.

write_cell_data raises a clear error for functions in a quadrature
space, which cannot be interpolated into DG-0.

The openCARP tests run for both orderings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

2 participants