xESMF icon indicating copy to clipboard operation
xESMF copied to clipboard

[Bug?] `ValueError: mask must have the same shape as the latitude/longitudecoordinates, got: mask.shape = (72, 144), lon.shape = (144, 72)`

Open tomvothecoder opened this issue 4 months ago • 0 comments

What happened?

Hello, I think I found a bug in xESMF when a mask variable is present in the ds_out passed to xe.Regridder().

  1. In xcdat.create_grid() we add a mask variable with the same shape as the grid being created, (lon, lat) order
    • Example: mask has a shape of (lon: 144, lat: 72)
    • Code: https://github.com/xCDAT/xcdat/blob/58dac7246be1fefbe22c382c746bf73f2418bbb9/xcdat/regridder/grid.py#L533
  2. xESMF extracts lon and lat then creates a 2D mesh grid using np.meshgrid, which transposes the dimensions
    • Example: lon and lat both have shape (72, 144)
    • Code: https://github.com/pangeo-data/xESMF/blob/abd266c94645d364006f4e852f85773abfcfa230/xesmf/frontend.py#L153
  3. xESMF attempts to create the grid using lon, lat, and mask (transposed again), but the shapes don't align resulting in the error: ValueError: mask must have the same shape as the latitude/longitudecoordinates, got: mask.shape = (72, 144), lon.shape = (144, 72)
    • Code: https://github.com/pangeo-data/xESMF/blob/abd266c94645d364006f4e852f85773abfcfa230/xesmf/frontend.py#L161-L162

Minimal Complete Verifiable Example (MVCE)

Input data for the following minimal code: cmip5.ACCESS1-0.historical.r1i1p1.mo.pr.ver-1.1981-2005.interpolated.regrid2.global.AC.nc.zip

import xcdat as xc

ncfile = "cmip5.ACCESS1-0.historical.r1i1p1.mo.pr.ver-1.1981-2005.interpolated.regrid2.global.AC.nc"
ds = xc.open_dataset(ncfile)

# target grid set up
lat1: float = -90.0
lat2: float = 90.0
lon1: float = 0.0
lon2: float = 360.0
target_grid_resolution = "2.5x2.5"

res = target_grid_resolution.split("x")
lat_res = float(res[0])
lon_res = float(res[1])
start_lat = lat1 + lat_res / 2.0
start_lon = lon1 + lon_res / 2.0
end_lat = lat2 - lat_res / 2
end_lon = lon2 - lon_res / 2

t_grid = xc.create_uniform_grid(
    start_lat, end_lat, lat_res, start_lon, end_lon, lon_res
)

# regrid
# Note: This calls xe.Regridder
ds_regridded = ds.regridder.horizontal("pr", t_grid, tool="xesmf", method="bilinear")

Relevant log output

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[1], [line 51](vscode-notebook-cell:?execution_count=1&line=51)
     24 t_grid = xc.create_uniform_grid(
     25     start_lat, end_lat, lat_res, start_lon, end_lon, lon_res
     26 )
     29 # # Create target grid without xcdat
     30 # lats = np.arange(start_lat, end_lat + lat_res, lat_res)
     31 # lons = np.arange(start_lon, end_lon + lon_res, lon_res)
   (...)     49 
     50 # # regrid
---> [51](vscode-notebook-cell:?execution_count=1&line=51) ds_regridded = ds.regridder.horizontal("pr", t_grid, tool="xesmf", method="bilinear")

File ~/repositories/xCDAT/xcdat/xcdat/regridder/accessor.py:232, in RegridderAccessor.horizontal(self, data_var, output_grid, tool, **options)
    230 input_grid = _get_input_grid(self._ds, data_var, ["X", "Y"])
    231 regridder = regrid_tool(input_grid, output_grid, **options)
--> [232](https://file+.vscode-resource.vscode-cdn.net/Users/vo13/repositories/xCDAT/xcdat/~/repositories/xCDAT/xcdat/xcdat/regridder/accessor.py:232) output_ds = regridder.horizontal(data_var, self._ds)
    234 return output_ds

File ~/repositories/xCDAT/xcdat/xcdat/regridder/xesmf.py:180, in XESMFRegridder.horizontal(self, data_var, ds)
    173 # Align output grid dims with input grid dims, self._input_grid is derived from ``ds``.
    174 self._output_grid = self._output_grid.transpose(
    175     *[x for x in input_da.dims if x in self._output_grid],
    176     ...,
    177     missing_dims="ignore",
    178 )
--> [180](https://file+.vscode-resource.vscode-cdn.net/Users/vo13/repositories/xCDAT/xcdat/~/repositories/xCDAT/xcdat/xcdat/regridder/xesmf.py:180) regridder = xe.Regridder(
    181     self._input_grid,
    182     self._output_grid,
    183     method=self._method,
    184     **self._extra_options,
    185 )
    187 output_da = regridder(input_da, keep_attrs=True)
    189 output_ds = xr.Dataset({data_var: output_da}, attrs=ds.attrs)

File /opt/miniconda3/envs/xcdat_784/lib/python3.13/site-packages/xesmf/frontend.py:923, in Regridder.__init__(self, ds_in, ds_out, method, locstream_in, locstream_out, periodic, parallel, **kwargs)
    921     grid_out, shape_out, output_dims = ds_to_ESMFlocstream(ds_out)
    922 else:
--> [923](https://file+.vscode-resource.vscode-cdn.net/opt/miniconda3/envs/xcdat_784/lib/python3.13/site-packages/xesmf/frontend.py:923)     grid_out, shape_out, output_dims = ds_to_ESMFgrid(ds_out, need_bounds=need_bounds)
    925 # Create the BaseRegridder
    926 super().__init__(
    927     grid_in,
    928     grid_out,
   (...)    933     **kwargs,
    934 )

File /opt/miniconda3/envs/xcdat_784/lib/python3.13/site-packages/xesmf/frontend.py:162, in ds_to_ESMFgrid(ds, need_bounds, periodic, append)
    160 # tranpose the arrays so they become Fortran-ordered
    161 if mask is not None:
--> [162](https://file+.vscode-resource.vscode-cdn.net/opt/miniconda3/envs/xcdat_784/lib/python3.13/site-packages/xesmf/frontend.py:162)     grid = Grid.from_xarray(lon.T, lat.T, periodic=periodic, mask=mask.T)
    163 else:
    164     grid = Grid.from_xarray(lon.T, lat.T, periodic=periodic, mask=None)

File /opt/miniconda3/envs/xcdat_784/lib/python3.13/site-packages/xesmf/backend.py:138, in Grid.from_xarray(cls, lon, lat, periodic, mask)
    136 grid_mask = mask.astype(np.int32)
    137 if not (grid_mask.shape == lon.shape):
--> [138](https://file+.vscode-resource.vscode-cdn.net/opt/miniconda3/envs/xcdat_784/lib/python3.13/site-packages/xesmf/backend.py:138)     raise ValueError(
    139         'mask must have the same shape as the latitude/longitude'
    140         'coordinates, got: mask.shape = %s, lon.shape = %s' % (mask.shape, lon.shape)
    141     )
    142 grid.add_item(ESMF.GridItem.MASK, staggerloc=ESMF.StaggerLoc.CENTER, from_file=False)
    143 grid.mask[0][:] = grid_mask

ValueError: mask must have the same shape as the latitude/longitudecoordinates, got: mask.shape = (72, 144), lon.shape = (144, 72)

Anything else we need to know?

No response

Environment

Latest xCDAT and xESMF

tomvothecoder avatar Aug 21 '25 23:08 tomvothecoder