Skip to content

Revamp approach to projecting spherical meshes#51

Merged
andrewdnolan merged 18 commits intoE3SM-Project:mainfrom
andrewdnolan:projection
Mar 3, 2026
Merged

Revamp approach to projecting spherical meshes#51
andrewdnolan merged 18 commits intoE3SM-Project:mainfrom
andrewdnolan:projection

Conversation

@andrewdnolan
Copy link
Copy Markdown
Collaborator

@andrewdnolan andrewdnolan commented Feb 10, 2026

Projection Support as it stands:

As of this PR, we only support a subset of cartopy projections, which are mostly rectangular projections. Because these projections are rectangular, we can assume they have a constant period across projection boundaries. Using this period we treat spherical meshes the same way we treat planar periodic meshes; manually correcting the coordinates of "nodes" that wrap across a periodic boundary.

Limitations of the current approach:

  • It is not a valid assumptions that all projections have a constant period (e.g. ccrs.AzimuthalEquidistant). Therefore, we can not support certain desired projections (e.g. ccrs.Orthographic) with out current approach.
  • When we correct patches that cross the projection boundary, we do it for each variable location separately (i.e. cell, edge, vertex patches). Moreover, the corrections only applies to patch array used by mosaic.polypcolor and will require doing totally redundant work for contours (Implement cell contouring on the native mesh #52) and any other additional plotting methods added in the future

Proposed Revision:

In this PR I propose that instead of worrying about correcting cell coordinates that wrap across the projection boundary, we instead cull those cells along with other invalid cells and cells that lie outside of the projection boundary from the mesh dataset stored by mosaic.Descriptor. Because cells that cross the projection boundary will be culled, there will not be any need for special handling or correcting, and we should be able to support nearly all cartopy projections.

Benefits of the proposed solution:

  • Support for nearly all cartopy projections! (UTM projections for spherical meshes are the only unsupported in this PR, though I think I could do some more debugging and get that to work as well).
  • Projection correction is done once for the entire mesh; is one time cost that applies to all plotting methods.
  • Because cells as culled along/outside projections boundaries, projected meshes are effectively planar non-periodic and contouring PR (Implement cell contouring on the native mesh #52) will work with no special handling.
  • While dependent on projection, this approach will significantly speed up plotting for projections that cover a limited extend of the globe, because cells outside the domain are culled. In these cases, fewer patches need to be drawn, which is the current performance bottle neck.

Limitations of the current approach:

  • Because cells are culled along the along/outside projections boundaries, there will be missing data. But at any reasonable resolution this is indistinguishable for most projections. I don't see this outweighing the benefits.

Closes #50, #21

used by descriptor. Will be used for culling cells that lie outside
or cross the projection boundary. As part of the culling, the
connectivity information will be altered so that mesh is not periodic
across projection boundary
@andrewdnolan andrewdnolan self-assigned this Feb 10, 2026
@andrewdnolan andrewdnolan added the enhancement New feature or request label Feb 10, 2026
@andrewdnolan
Copy link
Copy Markdown
Collaborator Author

Here are some example plots. As you can see for the QU.240km mesh, the culling along the projection boundary is quite apparent, especially for the rectangular projections. Happy to share actual figures as pngs or pdfs.

LambertConformal Mollweide PlateCarree Orthographic NorthPolarStereo

The ccrs.LambertConformal(), ccrs.Orthographic(), ccrs.Mollweide() projections are new addition as of this PR.

@andrewdnolan
Copy link
Copy Markdown
Collaborator Author

andrewdnolan commented Feb 11, 2026

The new method of determining invalid cells is also fast! And again this a onetime cost that will support all plotting methods.

For the v3 hi-res, it takes me

import mosaic
import xarray as xr

ds = xr.open_dataset("mpaso.RRSwISC6to18E3r5.20250205.nc")

%%timeit
descriptor = mosaic.Descriptor(
    ds, use_latlon=True, projection=ccrs.Mollweide(), transform=ccrs.Geodetic()
)
# 5.69 s ± 35.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

The timeit result does not included the xr.open_dataset, just the mosaic.Descriptor initialization. All identification of problem cells and culling happens at initialization, so that should be reflective of the timing.

@andrewdnolan andrewdnolan marked this pull request as ready for review February 11, 2026 03:32
@andrewdnolan
Copy link
Copy Markdown
Collaborator Author

@xylar @cbegeman There's still some more to do for this PR with regards to testing and documentation.

But, I think this is ready for a review of the algorithmic approach: culling invalid cells. This allows us to support (basically) all cartopy projections and is indiscernible for any scientifically useful mesh. But, this will cause some artifacts for very coarse meshes which could causes issues in polaris. (I have some ideas of hacky ways we can keep things working in polaris, more to come on that).

I'm curious to hear what you think about this. Any comments and/or concerns would be greatly appreciated! (Happy to make any test plots for a given mesh/projections that could be helpful in your evaluations).

@cbegeman
Copy link
Copy Markdown
Collaborator

@andrewdnolan This seems like a great alternative! Are there any implications for plotting planar meshes?

@andrewdnolan
Copy link
Copy Markdown
Collaborator Author

Are there any implications for plotting planar meshes?

Nope! Planar non-periodic and planar periodic are unaltered by these changes.

With regards to very coarse spherical meshes in polaris: what I'm thinking is that we can use the lat/lon coordinates, but treat them as planar periodic meshes so that the wrapping is fixed and the wrapped cells are mirrored across the projection boundary. With this approach, we'd only be able to do it for ccrs.PlateCarree. For other projections, we'd have to cull the mesh as demonstrated here. I'm thinking about polaris test cases like "cosine bell" and "geostrophic", in particular.

xylar
xylar previously approved these changes Feb 19, 2026
Copy link
Copy Markdown
Collaborator

@xylar xylar left a comment

Choose a reason for hiding this comment

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

@andrewdnolan, thanks for walking me through this! I think it looks great and I'm happy to approve. Let me know if you'd like me to either do some more testing or look at specific code.

cbegeman
cbegeman previously approved these changes Feb 22, 2026
Copy link
Copy Markdown
Collaborator

@cbegeman cbegeman left a comment

Choose a reason for hiding this comment

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

Approving on the basis of @andrewdnolan's explanation and testing and brief visual inspection of code changes. Thanks for giving this careful thought @andrewdnolan!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR revamps how spherical MPAS meshes are handled under Cartopy projections by switching from “periodic wrapping/correction” of patches to proactively culling cells (and dependent edges/vertices) that become invalid or cross projection boundaries after reprojection. This aims to unlock support for most Cartopy projections (including azimuthal ones) and reduce redundant per-plot correction work.

Changes:

  • Add mesh-culling utilities and centroid computation to support projection-aware culling.
  • Update Descriptor to transform coordinates once, compute a cull mask, and store a culled internal mesh for plotting.
  • Expand/adjust tests and docs to reflect the new projection/culling behavior and broader projection coverage.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
mosaic/descriptor.py Transforms mesh coords, computes cull masks for projections, and culls spherical meshes instead of wrapping.
mosaic/utils.py Adds cull_mesh, centroid utilities, and updates invalid-patch detection API to work on raw patch arrays.
mosaic/polypcolor.py Updates array-location parsing to support culled-vs-unculled inputs via lookup tables.
tests/test_spherical.py Expands projection coverage by iterating over Cartopy projections (excluding explicitly unsupported).
tests/test_planar_periodic.py Switches patch validity checks to the new get_invalid_patches(patches) API.
tests/test_polypcolor.py Adds duck-typing and dimension/length compatibility tests for culled vs unculled arrays.
tests/test_utils.py Adds tests for compute_cell_centroid correctness and padding validation.
docs/user_guide/* + docs/index.md Reorganizes/updates docs around spherical vs planar periodic behavior and adds new pages.
pyproject.toml / dev-environment.txt / .pre-commit-config.yaml Updates tooling/deps (docs extras, dev env, mypy hook rev).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mosaic/descriptor.py Outdated
Comment thread mosaic/descriptor.py Outdated
Comment thread tests/test_utils.py
Comment thread docs/index.md Outdated
Comment thread docs/user_guide/spherical_mesh_support.md
Comment thread tests/test_utils.py
Comment thread mosaic/utils.py Outdated
Comment thread mosaic/utils.py
Comment thread mosaic/polypcolor.py
Comment thread mosaic/descriptor.py Outdated
andrewdnolan and others added 5 commits March 2, 2026 12:17
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
handles default transfrom based on `is_spherical` and `latlon` attributes
Avoids possible issues with `-1` meaning no connecivity
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

mosaic/descriptor.py:242

  • The projection property is annotated as always returning a CRS, but this setter accepts None and the constructor defaults projection to None. This makes the type contract inconsistent and can cause mypy issues. Consider annotating the getter/setter as CRS | None to match actual behavior.
    @property
    def projection(self) -> CRS:
        """The target projection for plotting."""
        return self._projection

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/user_guide/spherical_mesh_support.md
Comment thread mosaic/utils.py Outdated
Comment thread tests/test_spherical.py
Comment thread tests/test_planar_periodic.py
Comment thread mosaic/utils.py Outdated
Comment thread mosaic/descriptor.py Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for Azimuthal Projections

4 participants