From a397c3163e12e11f6e19d75b0bdfc35655504084 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 6d4f34f775f3c610dffdb51a9b7b952795b72802 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(