From 7dcb3e0be373048a532110ef1944c1f38501f0fb Mon Sep 17 00:00:00 2001 From: Rajeev Jain Date: Thu, 6 Aug 2026 09:34:27 -0500 Subject: [PATCH 1/2] Restore cached face_areas fast path in calculate_total_face_area The HEALPix fast path assigned result but did not return, so it was always overwritten by the recomputed quadrature sum. Restores the else branch and tightens RTOL back to 1e-7. --- test/grid/grid/test_areas.py | 27 +++++++++++++++++++++++++++ uxarray/grid/grid.py | 16 ++++++++-------- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/test/grid/grid/test_areas.py b/test/grid/grid/test_areas.py index f11cbde8d..f1d99f74f 100644 --- a/test/grid/grid/test_areas.py +++ b/test/grid/grid/test_areas.py @@ -169,3 +169,30 @@ def test_face_areas_fesom(gridpath): uxgrid = ux.open_grid(gridpath("ugrid", "fesom", "fesom.mesh.diag.nc")) total_area = uxgrid.calculate_total_face_area() nt.assert_almost_equal(total_area, 8.3780, decimal=4) + + +def test_total_face_area_healpix_uses_cached_equal_areas(): + """Default args must reuse the cached ``face_areas``, not recompute. + + HEALPix faces are exactly equal-area, so the cached sum is exactly 4*pi. + Recomputing via quadrature drifts and loses that property. + """ + uxgrid = ux.Grid.from_healpix(zoom=2) + + total_area = uxgrid.calculate_total_face_area() + + nt.assert_allclose(total_area, np.sum(uxgrid.face_areas.values), rtol=0) + nt.assert_allclose(total_area, 4 * np.pi, rtol=1e-12) + + +def test_total_face_area_honors_quadrature_kwargs(): + """Non-default quadrature settings must still trigger a fresh computation.""" + uxgrid = ux.Grid.from_healpix(zoom=2) + + recomputed = uxgrid.calculate_total_face_area(quadrature_rule="gaussian", order=2) + + nt.assert_allclose( + recomputed, + np.sum(uxgrid.compute_face_areas(quadrature_rule="gaussian", order=2)), + rtol=0, + ) diff --git a/uxarray/grid/grid.py b/uxarray/grid/grid.py index 9bd17e8a2..ec080f0e6 100644 --- a/uxarray/grid/grid.py +++ b/uxarray/grid/grid.py @@ -1992,16 +1992,16 @@ def calculate_total_face_area( and not latitude_adjusted_area ): result = np.sum(self.face_areas.values) - - result = np.sum( - self.compute_face_areas( - quadrature_rule=quadrature_rule, - order=order, - latitude_adjusted_area=latitude_adjusted_area, + else: + result = np.sum( + self.compute_face_areas( + quadrature_rule=quadrature_rule, + order=order, + latitude_adjusted_area=latitude_adjusted_area, + ) ) - ) - RTOL = 1e-6 # 1e-7 had warnings in existing CI tests (as of 2026-08-05), 1e-6 did not. + RTOL = 1e-7 if result > 4 * np.pi * self.sphere_radius**2 * (1 + RTOL): warnings.warn( f"Total face area (={result}) exceeds the surface area of the whole sphere " From b472009aa8809a96d415d93ea5f0bcf501806727 Mon Sep 17 00:00:00 2001 From: Sam Evans <47793072+Sevans711@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:33:53 -0400 Subject: [PATCH 2/2] comment to say RTOL face areas is mostly arbitrary --- uxarray/grid/grid.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/uxarray/grid/grid.py b/uxarray/grid/grid.py index ec080f0e6..eaa328b40 100644 --- a/uxarray/grid/grid.py +++ b/uxarray/grid/grid.py @@ -2001,6 +2001,8 @@ def calculate_total_face_area( ) ) + # Choose RTOL. Mostly just an arbitrary decision.... + # but noting that 1e-9 had warnings in existing CI tests (as of 2026-08-06), while 1e-8 did not. RTOL = 1e-7 if result > 4 * np.pi * self.sphere_radius**2 * (1 + RTOL): warnings.warn(