Skip to content

Build mesh filter dataframe columns from mesh.indices - #4095

Open
GuySten wants to merge 2 commits into
openmc-dev:developfrom
GuySten:mesh-dataframe-simplification
Open

Build mesh filter dataframe columns from mesh.indices#4095
GuySten wants to merge 2 commits into
openmc-dev:developfrom
GuySten:mesh-dataframe-simplification

Conversation

@GuySten

@GuySten GuySten commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Build mesh filter dataframe columns from mesh.indices

No change to any existing dataframe. Verified column-for-column against
develop for every mesh type -- regular (2D and 3D), rectilinear, cylindrical,
spherical and unstructured -- for both MeshFilter and MeshSurfaceFilter.

Problem

MeshFilter.get_pandas_dataframe reconstructs mesh element indices per axis,
including a special case for the one mesh type that is 0-based:

idx_start = 0 if isinstance(self.mesh, openmc.UnstructuredMesh) else 1

for label, dim_size in zip(self.mesh.axis_labels, self.mesh.dimension):
    filter_dict[mesh_key, label] = _repeat_and_tile(
        np.arange(idx_start, idx_start + dim_size), stride, data_size)
    stride *= dim_size

MeshSurfaceFilter.get_pandas_dataframe does the same thing again with a
different stride.

Every mesh class already exposes exactly this as indices, in bin order and
with the right base. The filter is reimplementing it, which means the index
convention for a mesh type is stated in two places, and a new mesh type needs
filter.py edited to get correct columns. It also assumes every mesh has one
index per axis with a fixed per-axis stride, which is true of the current mesh
types but is a property of those meshes rather than of meshes in general.

Changes

Both methods now take the index tuples from the mesh:

columns = [(mesh_key, label) for label in self.mesh.axis_labels]
indices = _repeat_and_tile(list(self.mesh.indices), stride, data_size)
return pd.DataFrame(indices, columns=columns)

_repeat_and_tile gains axis=0 on its np.repeat call so that an index tuple
is repeated as a row rather than being flattened.

The isinstance(self.mesh, openmc.UnstructuredMesh) special case goes away, as
does the duplicated per-axis loop in the surface filter. _mesh_current_names
is untouched.

Testing

New tests/unit_tests/test_mesh_filter_dataframe.py, parametrized over regular
3D, regular 2D, rectilinear, cylindrical and spherical, plus a separate
unstructured case:

  • column names match the mesh's axis_labels, with surf appended last for the
    surface filter
  • rows are exactly list(mesh.indices), in order
  • structured meshes stay 1-based: first row all ones, last row equal to
    mesh.dimension
  • for the surface filter, surface names cycle fastest and the element index is
    constant across one element's surface bins
  • unstructured meshes stay 0-based, with a single element_index column

These pin the conventions the implementation now depends on, so a future change
to indices cannot quietly alter every user's dataframe.

I also diffed the full dataframe output between develop and this branch for
all five mesh types and both filters: columns, column order, row counts and
values are identical.

Notes

On the 0-based case specifically: UnstructuredMesh already reports

axis_labels -> ('element_index',)
indices     -> [(i,) for i in range(self.n_elements)]

so the removed idx_start branch was reproducing what indices already gave.
The old code built np.arange(0, n_elements) for the same single column, and
the two agree element for element.

This is the first of several pieces split out of #3857, and is useful
independently of that work: it removes the duplicated index logic and the mesh
type isinstance from filter.py.

Checklist

  • I have performed a self-review of my own code
  • I have run clang-format (version 18) on any C++ source files (if applicable)
  • I have followed the style guidelines for Python source files (if applicable)
  • I have made corresponding changes to the documentation (if applicable)
  • I have added tests that prove my fix is effective or that my feature works (if applicable)

@GuySten
GuySten marked this pull request as ready for review August 31, 2026 21:15
@GuySten
GuySten requested a review from paulromano August 31, 2026 21:15
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