Skip to content

Fix Exodus encode treating every face as full width - #1752

Draft
cmdupuis3 wants to merge 6 commits into
mainfrom
cmd/exodus_encode_fill_value
Draft

cmdupuis3 wants to merge 6 commits into
mainfrom
cmd/exodus_encode_fill_value

Conversation

@cmdupuis3

@cmdupuis3 cmdupuis3 commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #1753

Overview

_encode_exodus finds the end of each face with np.where(row == -1), but connectivity padding is stored as INT_FILL_VALUE. The comparison never matches, so every face is counted at the maximum width. Two things follow: a mixed mesh collapses into a single element block carrying the wrong element type (triangles and quads all written as SHELL4), and the padding itself is written out as INT_FILL_VALUE + 1 in a slot where a node index belongs.

The fix matches INT_FILL_VALUE. Element blocks then split by face size, which is what Exodus requires — blocks are homogeneous.

One note for reviewers: because blocks now genuinely split, a mixed mesh is regrouped by face size on write, and nothing yet records that permutation, so the mesh reloads reordered. Two existing round-trip tests therefore compare the Exodus faces as a set in this PR instead of element-wise — test_mixed_exodus (on the mixed.exo fixture) and test_grid_ugrid_exodus_roundtrip (on outRLL1deg, which has polar triangles among its quads). The follow-up (cmd/exodus_block_reorder) records the ordering and restores both strict comparisons. This PR is green on its own, but it does temporarily loosen those two assertions.

PR Checklist

General

  • An issue is created and linked
  • Added appropriate labels (if your uxarray repo permissions allow it)
  • Filled out Overview and Expected Usage (if applicable) sections

Testing & Benchmarking

  • There is adequate test coverage of changes from this PR (add new tests if needed)

Documentation and Examples

  • Docstrings updated with any function changes, and included in all new functions
  • User (public) functions added to docs/api.rst; internal (private) function names start with an underscore (_)

AI Disclosure

AI Usage: Claude Opus 5

  • I have tested and take responsibility for all AI-generated content in my PR.

`_encode_exodus` looked for padding with `row == -1`, but connectivity is
padded with `INT_FILL_VALUE`. The match never fired, so every face was
counted at the maximum width: a mixed mesh collapsed into a single block
with the wrong element type, and the padding itself was written out as
`INT_FILL_VALUE + 1` where a node index belongs.

Match `INT_FILL_VALUE` instead. Blocks now split by face size, which is what
Exodus requires -- element blocks are homogeneous -- so a mixed mesh is
genuinely regrouped on write and reloads permuted. Two existing round-trip
tests, `test_mixed_exodus` and `test_grid_ugrid_exodus_roundtrip`, compare
the Exodus faces as a set for now; recording and restoring the original
order is a separate fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cmdupuis3 cmdupuis3 self-assigned this Sep 10, 2026
@cmdupuis3 cmdupuis3 added bug Something isn't working accuracy Related to mathematical accuracy of operations, such as numerical precision or rounding errors labels Sep 10, 2026

@jayeshkrishna jayeshkrishna left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change looks good. There are some suggestions on more test cases and modifying the comments/PR commit message,

The PR commit message could be changed to something like,

Exodus element blocks are homogeneous, so a mixed mesh has to be split by
face size (e.g. Mixed mesh containing TRI and SHELL elements are split
into two homogeneous blocks, the first block containing TRI elements and
the second block containing SHELL elements). In the face node connectivity
array the user specifies padding for shorter elements using a fillvalue.
This fillvalue is frequently specified by users as -1. However note that
this fillvalue/padding is internally stored as INT_FILL_VALUE in uxarray.
See PR #241 for details.

The _encode_exodus function was incorrectly finding the end of each face with
np.where(row == -1). The comparison never matches if the fillvalue is different
(or not specified by the user), so every face (including faces with padding) is counted at the maximum width of the connectivity array. Two things follow:
(1) a mixed mesh collapses into a single element block carrying the wrong
element type (e.g. triangles and quads all written as SHELL4)
(2) the padding itself is written out as INT_FILL_VALUE + 1 in a slot where
a node index belongs.

The fix matches node values to INT_FILL_VALUE to detect each face. Element
blocks are then split by face size, which is what Exodus requires (blocks are
homogeneous).

Comment thread test/io/test_exodus.py
def test_encode_exodus_mixed_blocks():
"""Faces of different sizes go into separate, correctly typed blocks.

Exodus element blocks are homogeneous, so a mixed mesh has to be split by

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to remove references to the issue (looking for padding -1) that this test was added for. Instead adding info on the test itself (what its testing).

I have a slightly modified/extended version of the test below here - https://gist.github.com/jayeshkrishna/5648aad04bd0ef1ef1acb5f370fed357 . Please feel free to modify and incorporate it as needed

Comment thread uxarray/io/_exodus.py
# Find the index of the first fill value (-1)
fill_val_idx = np.where(row == -1)[0]
# Find the index of the first fill value. Padding is stored as
# INT_FILL_VALUE, not -1; matching on -1 never fires, so every face is

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, you might want to remove reference to the old buggy check/result. Just have the comment shortened to something like,

# Find the index of the first fill value. Note: Padding is stored as
# INT_FILL_VALUE (not -1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accuracy Related to mathematical accuracy of operations, such as numerical precision or rounding errors bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exodus encode treating every face as full width

2 participants