YetAnotherCoupler 3.2.0_a
Loading...
Searching...
No Matches
grid_utils.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
3# Copyright (c) 2024 The YAC Authors
4#
5# SPDX-License-Identifier: BSD-3-Clause
6
7from netCDF4 import Dataset
8import numpy as np
9import re
10import cartopy.crs as ccrs
11from pathlib import Path
12
13
14def to_numpy_deg(var):
15 if "rad" in var.units:
16 return np.rad2deg(var)
17 else:
18 return var # assuming rad else
19
20
21def lonlat2xyz(lon, lat):
22 lon = np.deg2rad(lon)
23 lat = np.deg2rad(lat)
24 clat = np.cos(lat)
25 slat = np.sin(lat)
26 return np.stack([clat * np.cos(lon), clat * np.sin(lon), slat], axis=-1)
27
28
29def xyz2lonlat(xyz):
30 xyz = np.array(xyz)
31 lat = np.arcsin(xyz[..., 2])
32 lon = np.arctan2(xyz[..., 1], xyz[..., 0])
33 return np.rad2deg(lon), np.rad2deg(lat)
34
35
36def spherical_mean(lon, lat):
37 m = np.mean(lonlat2xyz(lon, lat), axis=1)
38 norm_inv = 1./np.linalg.norm(m, axis=-1)
39 return xyz2lonlat(norm_inv[:, None] * m)
40
41
43 """
44 Base class for Grids
45 """
46 def __init__(self):
47 self.transform = ccrs.Geodetic()
48
49 def plot(self, ax, label=None, plot_kwargs={}):
50 edge_vertices = self.edge_vertices
51 x = self.vlon[edge_vertices]
52 y = self.vlat[edge_vertices]
53
54 # exclude edges outside the extent
55 extent = ax.get_extent(crs=ccrs.PlateCarree())
56 mask = ((x[0, :] >= extent[0]) * (x[0, :] <= extent[1]) *
57 (y[0, :] >= extent[2]) * (y[0, :] <= extent[3])) + \
58 ((x[1, :] >= extent[0]) * (x[1, :] <= extent[1]) *
59 (y[1, :] >= extent[2]) * (y[1, :] <= extent[3]))
60
61 ax.plot(x[:, mask], y[:, mask], transform=self.transform,
62 **plot_kwargs)
63
64 text_kwargs = {}
65 if "color" in plot_kwargs:
66 text_kwargs["color"] = plot_kwargs["color"]
67 if "zorder" in plot_kwargs:
68 text_kwargs["zorder"] = plot_kwargs["zorder"]
69
70 def add_label_in_extent(x, y, txt):
71 mask = ((x >= extent[0]) * (x <= extent[1]) *
72 (y >= extent[2]) * (y <= extent[3]))
73
74 if hasattr(self,'slmsk'):
75 mask = mask * self.slmsk
76
77 for xx, yy, tt in zip(x[mask], y[mask], txt[mask]):
78 ax.text(xx, yy, tt, ha="center", va="center", transform=ccrs.PlateCarree(),
79 **text_kwargs)
80
81 if label == "cell":
82 add_label_in_extent(self.clon, self.clat,
83 np.arange(len(self.clon))+self.idx_offset)
84 elif label == "edge":
85 add_label_in_extent(self.elon, self.elat,
86 np.arange(len(self.elon))+self.idx_offset)
87 elif label == "vertex":
88 add_label_in_extent(self.vlon, self.vlat,
89 np.arange(len(self.vlon))+self.idx_offset)
90
91 def save_netcdf(self, filename):
92 with Dataset(filename, "w") as dataset:
93 vdim = dataset.createDimension("vertex", self.vlon.shape[0])
94 vlon = dataset.createVariable("vlon", "f8", dimensions=(vdim,))
95 vlon[:] = np.deg2rad(self.vlon)
96 vlon.units = "radian"
97 vlon.long_name = "vertex longitude"
98 vlon.standard_name = "grid_longitude"
99 vlat = dataset.createVariable("vlat", "f8", dimensions=(vdim,))
100 vlat.units = "radian"
101 vlat.long_name = "vertex latitude"
102 vlat.standard_name = "grid_latitude"
103 vlat[:] = np.deg2rad(self.vlon)
104 cdim = dataset.createDimension("cell", self.vertex_of_cell.shape[1])
105 nv = dataset.createDimension("nv", self.vertex_of_cell.shape[0])
106 vertex_of_cell = dataset.createVariable("vertex_of_cell", "i", dimensions=(nv,cdim))
107 vertex_of_cell[:] = self.vertex_of_cell
108 vertex_of_cell.long_name = "vertices of each cell"
109 if hasattr(self, "clon") and hasattr(self, "clat"):
110 clon = dataset.createVariable("clon", "f8", dimensions=(cdim,))
111 clon.units = "radian"
112 clon.long_name = "center longitude"
113 clon.standard_name = "grid_longitude"
114 clon[:] = np.deg2rad(self.clon)
115 clat = dataset.createVariable("clat", "f8", dimensions=(cdim,))
116 clat.units = "radian"
117 clat.long_name = "center latitude"
118 clat.standard_name = "grid_latitude"
119 clat[:] = np.deg2rad(self.clon)
120 if hasattr(self, "elon") and hasattr(self, "elat"):
121 edim = dataset.createDimension("edge", self.elon.shape[0])
122 elon = dataset.createVariable("elon", "f8", dimensions=(edim,))
123 elon[:] = self.elon
124 elat = dataset.createVariable("elat", "f8", dimensions=(edim,))
125 elat[:] = self.elon
126
127 def def_yac(self, gridname, locations):
128 import yac
129 # currently only grids with same number of vertices per cell are supported
130 yac_grid = yac.UnstructuredGrid(gridname,
131 [self.vertex_of_cell.shape[0]]*self.vertex_of_cell.shape[1],
132 np.deg2rad(self.vlon), np.deg2rad(self.vlat),
133 self.vertex_of_cell.T.flatten(),
134 self.transform == ccrs.PlateCarree())
135 coords = {yac.Location.CELL: (np.deg2rad(self.clon), np.deg2rad(self.clat)),
136 yac.Location.CORNER: (np.deg2rad(self.vlon), np.deg2rad(self.vlat))}
137 return [yac_grid.def_points(loc, *coords[loc]) for loc in locations]
138
139
141 """
142 Read grid from netCDF file like it is stored in ICON with fields
143 clon, clat, vlon, vlat, vertex_of_cell etc.
144 """
145 def __init__(self, string):
146 super().__init__()
147 filename = Path(string[5:]).expanduser() # truncate "icon:"
148 with Dataset(filename, "r") as dataset:
149 self.no_cells = dataset.dimensions["cell"].size
150 self.clonclon = to_numpy_deg(dataset["clon"])
151 self.clatclat = to_numpy_deg(dataset["clat"])
152 self.vlonvlon = to_numpy_deg(dataset["vlon"])
153 self.vlatvlat = to_numpy_deg(dataset["vlat"])
154 self.elonelon = to_numpy_deg(dataset["elon"])
155 self.elatelat = to_numpy_deg(dataset["elat"])
156 index_base = np.min(dataset["vertex_of_cell"])
157 self.vertex_of_cell = dataset["vertex_of_cell"] - index_base
158 self.edge_vertices = dataset["edge_vertices"] - index_base
159 self.adjacent_cell_of_edge = dataset["adjacent_cell_of_edge"] - index_base
160 self.idx_offsetidx_offset = index_base
161
162
164 """
165 Generate Gaussian grid with an given resolution and extent
166 (currently only on an closed interval in lon)
167 """
168 def __init__(self, string):
169 super().__init__()
170 m = re.match(r"([gG])([0-9]+),([0-9]+)(,(-?[0-9]+(\.[0-9]+)?),(-?[0-9]+(\.[0-9]+)?),(-?[0-9]+(\.[0-9]+)?),(-?[0-9]+(\.[0-9]+)?))?", string)
171 if not m:
172 raise Exception(f"Defintion of Gaussian grid {string} does not match regular expression.")
173 self.idx_offsetidx_offset = 0 if m.groups()[0] == "g" else 1
174 if m.groups()[3] is not None:
175 extent = [float(m.groups()[4]), float(m.groups()[6]), float(m.groups()[8]), float(m.groups()[10])]
176 else:
177 extent = [-180, 180, -90, 90]
178 res = [int(m.groups()[1]), int(m.groups()[2])]
179
180 self.lon = np.linspace(extent[0], extent[1], res[0]+1, endpoint=True)
181 self.lat = np.linspace(extent[2], extent[3], res[1]+1, endpoint=True)[::-1]
182 self.vlonvlon, self.vlatvlat = [x.flatten() for x in np.meshgrid(self.lon, self.lat)]
183 self.clonclon, self.clatclat = [x.flatten() for x in
184 np.meshgrid(self.lon[:-1] + 0.5*np.abs(self.lon[1]-self.lon[0]),
185 self.lat[1:] + 0.5*np.abs(self.lat[1]-self.lat[0]))]
186 self.no_cells = len(self.clatclat)
187 yidx, xidx = np.mgrid[0:res[1], 0:res[0]]
188
189 self.vertex_of_cell = np.stack([(xidx + yidx*(res[0]+1)).flatten(),
190 ((xidx+1) + yidx*(res[0]+1)).flatten(),
191 (xidx + (yidx+1)*(res[0]+1)).flatten(),
192 ((xidx+1) + (yidx+1)*(res[0]+1)).flatten()])
193
194 yidx, xidx = np.mgrid[0:res[1]+1, 0:res[0]]
195 hedges = np.stack([(xidx + yidx*(res[0]+1)).flatten(),
196 ((xidx+1) + yidx*(res[0]+1)).flatten()])
197 yidx, xidx = np.mgrid[0:res[1], 0:res[0]+1]
198 vedges = np.stack([(xidx + yidx *(res[0]+1)).flatten(),
199 (xidx + (yidx+1)*(res[0]+1)).flatten()])
200 self.edge_vertices = np.hstack([hedges, vedges])
201 self.elonelon = 0.5*np.sum(self.vlonvlon[self.edge_vertices], axis=0)
202 self.elatelat = 0.5*np.sum(self.vlatvlat[self.edge_vertices], axis=0)
203 self.transformtransform = ccrs.PlateCarree()
204
205 def def_yac(self, gridname, locations):
206 import yac
207 lon_rad = np.deg2rad(self.lon)
208 lat_rad = np.deg2rad(self.lat)
209 yac_grid = yac.Reg2dGrid(gridname, lon_rad, lat_rad)
210 coords = {yac.Location.CELL: (0.5*(lon_rad[1:]+lon_rad[:-1]), 0.5*(lat_rad[1:]+lat_rad[:-1])),
211 yac.Location.CORNER: (lon_rad, lat_rad)}
212 return [yac_grid.def_points(loc, *coords[loc]) for loc in locations]
213
214
216 """
217 HealPix Grid
218 """
219 def __init__(self, string):
220 super().__init__()
221 import healpy
222 m = re.match(r"([hH]([0-9]+))([rn])?", string)
223 if not m:
224 raise Exception(f"Defintion of HealPix grid {string} does not match regular expression.")
225 self.idx_offsetidx_offset = 0 if m.groups()[0] == "h" else 1
226 self.nside = int(m.groups()[1])
227 self.nest = m.groups()[2] != "r"
228 ncells = healpy.pixelfunc.nside2npix(self.nside)
229 centers_xyz = np.stack(
230 healpy.pixelfunc.pix2vec(self.nside, range(ncells), nest=self.nest),
231 axis=-1,
232 )
233 self.clonclon, self.clatclat = xyz2lonlat(centers_xyz)
234 boundaries_xyz = (
235 healpy.boundaries(self.nside, range(ncells), nest=self.nest)
236 .transpose(0, 2, 1)
237 .reshape(-1, 3)
238 )
239 verts_xyz, quads = np.unique(boundaries_xyz, return_inverse=True, axis=0)
240 self.vlonvlon, self.vlatvlat = xyz2lonlat(verts_xyz)
241 self.vertex_of_cell = quads.reshape(-1, 4).T
242 edges = np.hstack([self.vertex_of_cell[(i, (i+1) % 4), :] for i in range(4)])
243 edges = np.sort(edges, axis=0)
244 self.edge_vertices = np.unique(edges, axis=1)
245
246
248 def __init__(self, string):
249 super().__init__()
250 self.simple_dual = string[0] == "D"
251 prim_grid = get_grid(string[5:]) # truncate "dual:"
252 self.clonclon = prim_grid.vlon
253 self.clatclat = prim_grid.vlat
254 # compute corners
255 if hasattr(prim_grid, "clon") and hasattr(prim_grid, "clat"):
256 elem_centers_lon, elem_centers_lat = prim_grid.clon, prim_grid.clat
257 else:
258 elem_centers_lon, elem_centers_lat = spherical_mean(
259 prim_grid.vlon[prim_grid.vertex_of_cell], prim_grid.vlat[prim_grid.vertex_of_cell])
260
261 boundary_edges = (prim_grid.adjacent_cell_of_edge < 0)
262 is_boundary = sum(boundary_edges) != 0
263 boundary_nodes, bnodes_inverse = np.unique(
264 prim_grid.edge_vertices[:, boundary_edges[0, :] + boundary_edges[1, :]], return_inverse=True)
265 bnodes_inverse = bnodes_inverse.reshape(2, -1)
267
268 if self.simple_dual:
269 if hasattr(prim_grid, "elon") and hasattr(prim_grid, "elat"):
270 edge_centers_lon, edge_centers_lat = prim_grid.elon[is_boundary], prim_grid.elat[is_boundary]
271 else:
272 edge_centers_lon, edge_centers_lat = spherical_mean(
273 prim_grid.vlon[prim_grid.edge_vertices[:, is_boundary].T], prim_grid.vlat[prim_grid.edge_vertices[:, is_boundary].T])
274 else:
275 if hasattr(prim_grid, "elon") and hasattr(prim_grid, "elat"):
276 edge_centers_lon, edge_centers_lat = prim_grid.elon, prim_grid.elat
277 else:
278 edge_centers_lon, edge_centers_lat = spherical_mean(
279 prim_grid.vlon[prim_grid.edge_vertices.T], prim_grid.vlat[prim_grid.edge_vertices.T])
280
281 nelem = len(elem_centers_lon)
282 nedges = len(edge_centers_lon)
283 self.vlonvlon = np.concatenate([edge_centers_lon, elem_centers_lon, prim_grid.vlon[boundary_nodes]])
284 self.vlatvlat = np.concatenate([edge_centers_lat, elem_centers_lat, prim_grid.vlat[boundary_nodes]])
285
286 if self.simple_dual:
287 self.edge_vertices = np.hstack([
288 nedges + prim_grid.adjacent_cell_of_edge[:, ~is_boundary], # connect adjacent cell centers of non-boundary edges
289 nedges + nelem + bnodes_inverse,
290 np.stack([range(sum(is_boundary)), nedges + prim_grid.adjacent_cell_of_edge[boundary_edges[0, is_boundary].astype(int), is_boundary]])
291 ])
292 self.adjacent_cell_of_edge = np.hstack([
293 prim_grid.edge_vertices[:, ~is_boundary],
294 np.stack([boundary_nodes[bnodes_inverse[0, :]], -1*np.ones(bnodes_inverse[0, :].shape, dtype=int)]),
295 prim_grid.edge_vertices[:, is_boundary],
296 ])
297 else:
298 self.edge_vertices = np.hstack(
299 [np.stack([np.flatnonzero(~boundary_edges[0, :]), nedges+prim_grid.adjacent_cell_of_edge[0, ~boundary_edges[0, :]]]),
300 np.stack([np.flatnonzero(~boundary_edges[1, :]), nedges+prim_grid.adjacent_cell_of_edge[1, ~boundary_edges[1, :]]]),
301 np.stack([np.flatnonzero(is_boundary), nedges + nelem + bnodes_inverse[0, :]]),
302 np.stack([np.flatnonzero(is_boundary), nedges + nelem + bnodes_inverse[1, :]])
303 ])
304 self.adjacent_cell_of_edge = np.hstack([
305 prim_grid.edge_vertices[:, ~boundary_edges[0, :]],
306 prim_grid.edge_vertices[:, ~boundary_edges[1, :]],
307 np.stack([boundary_nodes[bnodes_inverse[0, :]], -1*np.ones(bnodes_inverse[0, :].shape, dtype=int)]),
308 np.stack([boundary_nodes[bnodes_inverse[1, :]], -1*np.ones(bnodes_inverse[1, :].shape, dtype=int)]),
309 ])
310
311
313 def __init__(self, string):
314 super().__init__()
315 path = Path(string[6:]).expanduser()
316 nodes = np.fromfile(path / "nod2d.out", sep=" ")[1:].reshape(-1, 4)
317 self.vertex_of_cell = np.fromfile(path / "elem2d.out", sep=" ", dtype=int)[1:].reshape(-1, 3).T - 1
318 self.edge_vertices = np.fromfile(path / "edges.out", sep=" ", dtype=int).reshape(-1, 2).T - 1
319 self.vlonvlon = (((180 + nodes[:, 1]) % 360) - 180)
320 self.vlatvlat = nodes[:, 2]
322 self.adjacent_cell_of_edge = np.fromfile(path / "edge_tri.out", sep=" ", dtype=int).reshape(-1, 2).T - 1
323
324
326 def __init__(self, string):
327 super().__init__()
328 has_masks = string.count(':') == 3
329 path = string[6:].split(':')[0]
330 if has_masks:
331 mpath = string[6:].split(':')[1]
332 grid = string[6:].split(':')[2]
333 else:
334 grid = string[6:].split(':')[1]
335 with Dataset(path, "r") as dataset:
336 x_size = dataset.dimensions["x_"+grid].size
337 y_size = dataset.dimensions["y_"+grid].size
338 self.no_cells = x_size * y_size
339 self.no_crn = dataset.dimensions["crn_"+grid].size
340 self.clonclon = (((180 + np.asfortranarray(dataset[grid+".lon"]).flatten()) % 360) - 180)
341 self.clatclat = np.asfortranarray(dataset[grid+".lat"]).flatten()
342 self.vlonvlon = (((180 + np.asfortranarray(dataset[grid+".clo"]).flatten()) % 360) - 180)
343 self.vlatvlat = np.asfortranarray(dataset[grid+".cla"]).flatten()
344 self.vertex_of_cell = np.arange(self.no_cells*self.no_crn).reshape(self.no_crn,-1)
345 if has_masks:
346 with Dataset(mpath, "r") as dataset:
347 self.slmsk = np.asfortranarray(dataset[grid+".msk"]).flatten()
348 self.slmsk = self.slmsk == 0
349 self.vertex_of_cell = np.delete(self.vertex_of_cell,
350 np.where(self.slmsk == False)[0].tolist(), axis = 1)
351 edges = np.hstack([self.vertex_of_cell[(i, (i+1) % self.no_crn), :] for i in range(self.no_crn)])
352 edges = np.sort(edges, axis=0)
353 self.edge_vertices = np.unique(edges, axis=1)
354 self.elonelon = 0.5*np.sum(self.vlonvlon[self.edge_vertices], axis=0)
355 self.elatelat = 0.5*np.sum(self.vlatvlat[self.edge_vertices], axis=0)
357
358
359def get_grid(grid):
360 if grid.lower().startswith("icon:"):
361 return ICONGrid(grid)
362 if grid.lower().startswith("g"):
363 return GaussianGrid(grid)
364 if grid.lower().startswith("h"):
365 return HealPixGrid(grid)
366 if grid.lower().startswith("fesom:"):
367 return FesomGrid(grid)
368 if grid.lower().startswith("dual:"):
369 return DualGrid(grid)
370 if grid.lower().startswith("scrip:"):
371 return ScripGrid(grid)
372 else:
373 raise Exception(f"cannot parse grid description: {grid}")
374
375
376if __name__ == "__main__":
377 import argparse
378 parser = argparse.ArgumentParser(prog="grid_utils.py")
379 subparsers = parser.add_subparsers(dest='command', help='sub-command help')
380 parser_save_grid = subparsers.add_parser('save_grid', help='save a grid to netCDF file')
381 parser_save_grid.add_argument('grid', type=str, help='grid')
382 parser_save_grid.add_argument('filename', type=Path, help='filename')
383 args = parser.parse_args()
384 if args.command == "save_grid":
385 get_grid(args.grid).save_netcdf(args.filename)
__init__(self, string)
__init__(self, string)
Generate Gaussian grid with an given resolution and extent (currently only on an closed interval in l...
def_yac(self, gridname, locations)
__init__(self, string)
Base class for Grids.
Definition grid_utils.py:42
plot(self, ax, label=None, plot_kwargs={})
Definition grid_utils.py:49
save_netcdf(self, filename)
Definition grid_utils.py:91
def_yac(self, gridname, locations)
__init__(self, string)
Read grid from netCDF file like it is stored in ICON with fields clon, clat, vlon,...
__init__(self, string)
__init__(self, string)
A stuctured 2d Grid.
Definition yac.pyx:995
An unstuctured 2d Grid.
Definition yac.pyx:1094
spherical_mean(lon, lat)
Definition grid_utils.py:36
to_numpy_deg(var)
Definition grid_utils.py:14
lonlat2xyz(lon, lat)
Definition grid_utils.py:21
xyz2lonlat(xyz)
Definition grid_utils.py:29