Skip to content

Fix UgridIndex implementation, remove metaclass trickery#436

Open
rsignell wants to merge 2 commits into
Deltares:indexfrom
OpenScienceComputing:fix/pr373-index-fixes
Open

Fix UgridIndex implementation, remove metaclass trickery#436
rsignell wants to merge 2 commits into
Deltares:indexfrom
OpenScienceComputing:fix/pr373-index-fixes

Conversation

@rsignell

@rsignell rsignell commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR picks up where #373 left off and fixes the remaining broken tests and design issues.

Fixes the known bugs (#373 left TODO):

  • UgridIndex1d._variables_from_dataset() return signature fixed to (variables, options) tuple
  • UgridDataset(grids=...) and UgridDataset(obj, grids=...) paths implemented in wrap.py
  • UgridIndex.equals() uses value comparison (grid.equals()) not identity — fixes binary ops between DataArrays derived from same grid
  • UgridIndex.join(), reindex_like() added for xarray alignment support

Removes metaclass trickery (per reviewer feedback on #435):

  • Removes _UgridDataArrayMeta / _UgridDatasetMeta entirely — these existed only to make isinstance pass for plain xr.DataArray, which the reviewer correctly identified as "slightly cursed"
  • Adds is_ugrid_dataarray(obj) and is_ugrid_dataset(obj) predicate helpers, exported from the top-level namespace
  • Replaces all isinstance(obj, UgridDataArray/UgridDataset) checks with the new predicates in both library (~10 sites) and tests (~100 sites)

Fixes rename() method:

  • Previously calling obj.rename() on a UgridIndex-backed object failed because xarray tried to rename index-backing dimensions that no longer existed after stripping
  • Fix: drop_ugrid_index() before renaming, compute to_rename from the stripped object, then re-attach via factory

Other fixes:

  • UgridDataArrayAccessor.to_crs(), to_dataset(), from_structured2d() updated for new index architecture
  • UgridDatasetAccessor.from_dataset() restores coordinate attrs after copy; to_crs() and to_dataset() updated
  • ugridbase.sel_points(): only add dim_coords when indexing multiple grid dims simultaneously
  • regrid/: all obj.gridobj.ugrid.grid, obj.obj → direct DataArray usage; isinstance check order fixed
  • Test scalar comparisons result.min() >= disk.min() use .item() to avoid MergeError across different-grid DataArrays

Test plan

  • pytest tests/test_ugrid_dataset.py — all pass (2 pre-existing missing pymetis skips)
  • pytest tests/test_ugrid2d.py — all pass (1 pre-existing missing mapbox_earcut skip)
  • pytest tests/test_regrid/ — all 111 pass
  • Full suite: 504 passed, 5 skipped, 8 failed (all 8 are pre-existing missing optional deps: mapbox_earcut, pymetis, pytest_cases)

🤖 Generated with Claude Code

rsignell and others added 2 commits May 6, 2026 10:40
…dex refactor

Completes the UgridIndex-based architecture where UgridDataArray/UgridDataset
return plain xr.DataArray/xr.Dataset with embedded UgridIndex instead of
custom wrapper classes.

Library fixes:
- UgridIndex.equals(): use grid.equals() (value comparison) instead of
  identity check so binary ops between DataArrays from the same grid work
- UgridIndex: add join(), reindex_like() for xarray alignment support
- UgridIndex1d._variables_from_dataset(): fix return signature to match
  (variables, options) tuple expected by from_dataset()
- UgridDataset.__new__(): implement grids= path and obj+grids= path
- dataarray_accessor: fix to_crs(), to_dataset(), from_structured2d() for
  new API; call _update_coordinate_attrs after CRS transform
- dataset_accessor: fix from_dataset() to restore coord attrs after copy;
  fix to_crs() and to_dataset() for new API
- ugridbase.sel_points(): only add dim_coords when other_dims is non-empty
- regridder.py: reorder isinstance checks (UgridDataArray before xr.DataArray),
  strip UgridIndex from source before regridding structured result
- regrid/network.py, unstructured.py: fix obj.grid -> obj.ugrid.grid

Test fixes:
- Update .grid -> .ugrid.grid, .obj -> direct DataArray usage throughout
- Fix scalar comparisons result.min() >= disk.min() to use .item() to avoid
  MergeError when comparing 0-dim DataArrays from different grids
- Fix UgridDataArray constructor calls removing deprecated grid= keyword form
- Fix test_ugrid_dataset.py: remove .obj refs, fix multi-topology grid lookup,
  use .values for cross-grid comparisons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…checks

Per reviewer feedback, the metaclass (_UgridDataArrayMeta / _UgridDatasetMeta)
approach is "slightly cursed" and exists only to minimize test changes.

This commit removes the metaclasses entirely and makes the design principled:
- UgridDataArray and UgridDataset are plain factory classes whose __new__
  returns a plain xr.DataArray / xr.Dataset with a UgridIndex attached
- Two predicate helpers are added and exported: is_ugrid_dataarray(obj) and
  is_ugrid_dataset(obj), which check isinstance + _has_ugrid_index
- All isinstance(obj, UgridDataArray/UgridDataset) checks in library code
  are replaced with the predicate helpers (~10 sites)
- All isinstance checks in test files are replaced similarly (~100 sites)

Also fixes the rename() method in both accessor classes: previously calling
xarray's obj.rename() on a UgridIndex-backed object caused errors because
xarray tries to rename the index's coordinates (which span dimensions not
present after stripping). Fix: call drop_ugrid_index() first, compute
to_rename from the stripped object, then re-attach via UgridDataArray/Dataset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rsignell

rsignell commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

@Huite I pointed claude at your review comment in #435 and it responded with this. Please just regard this as an experiment -- and let me know if you find this annoying.

@rsignell

Copy link
Copy Markdown
Contributor Author

@Huite anything for me to do here -- I'm guessing you've been just too busy to look, right?

@Huite

Huite commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Hi @rsignell

That's right, I've reserved the week of June 15th to take a proper look at this. But I'm feeling fairly hopeful that I'll be able to get things merged in that week!

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