YAC 3.21.0
Yet Another Coupler
Loading...
Searching...
No Matches
yac_generate_weights.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
3# Copyright (c) 2025 The YAC Authors
4#
5# SPDX-License-Identifier: BSD-3-Clause
6
7
10
11try:
12 from mpi4py import MPI # calls MPI_Init with some special treatments
13except:
14 pass
15
16from yac.core import (
17 BasicGrid,
18 yac_location,
19 InterpField,
20 InterpolationStack,
21 compute_weights,
22 yac_weight_file_on_existing
23)
24
25import uxarray as ux
26import netCDF4
27import numpy as np
28from argparse import ArgumentParser
29import json
30from pathlib import Path
31import logging
32import re
33
34
35def load_grid(filename, gridname=None):
36 match filename:
37 case f if m := re.match(r"^healpix(\d{1,2})$", f):
38 return ux.Grid.from_healpix(zoom=int(m[1]))
39 case f if m := re.match(r"^reg((?P<lon_from>-?\d+\.?\d*):(?P<lon_to>-?\d+\.?\d*):)?(?P<lon_n>\d+),((?P<lat_from>-?\d+\.?\d*):(?P<lat_to>-?\d+\.?\d*):)?(?P<lat_n>\d+)$", f):
40 return ux.Grid.from_structured(lon=np.linspace(start=float(m["lon_from"] or 0),
41 stop=float(m["lon_to"] or 360),
42 num=int(m["lon_n"])),
43 lat=np.linspace(start=float(m["lat_from"] or -90),
44 stop=float(m["lat_to"] or 90),
45 num=int(m["lat_n"])),
46 )
47 case _:
48 if gridname is not None:
49 return ux.open_multigrid(filename, gridnames=[gridname])[gridname]
50 return ux.open_grid(filename)
51
52
53def get_coords(loc, uxgrid, yac_grid):
54 if loc == yac_location.YAC_LOC_CELL:
55 lon, lat = np.deg2rad([uxgrid.face_lon, uxgrid.face_lat])
56 elif loc == yac_location.YAC_LOC_CORNER:
57 lon = lat = None
58 elif loc == yac_location.YAC_LOC_EDGE:
59 lon, lat = np.deg2rad([uxgrid.edge_lon, uxgrid.edge_lat])
60 return yac_grid.add_coordinates(loc, lon, lat)
61
62
64 try:
65 return json.loads(s)
66 except Exception as e:
67 logging.debug(f"Cannot parse interpolation as json: {e}")
68 return s, {}
69
70
71yac_location.__str__ = lambda loc: loc.name
72
73
74parser = ArgumentParser(prog="yac_generate_weights.py", description="Generate YAC weights files")
75parser.add_argument("src_grid_path", type=str,
76 help=(
77 "Path to the source grid file, or a shorthand: "
78 "'healpix<N>' for a HEALPix grid at zoom level N (e.g. 'healpix5'), "
79 "or 'reg[lon_from:lon_to:]<lon_n>,[lat_from:lat_to:]<lat_n>' for a "
80 "regular structured grid (e.g. 'reg360,180' or 'reg-180:180:360,-90:90:181')."
81 )
82)
83parser.add_argument(
84 "--src-location",
85 type=lambda loc: yac_location[loc],
86 choices=[
87 yac_location.YAC_LOC_CELL,
88 yac_location.YAC_LOC_CORNER,
89 yac_location.YAC_LOC_EDGE,
90 ],
91 default=yac_location.YAC_LOC_CELL,
92)
93parser.add_argument("--src-mask-var", type=str, help="Variable name of the mask")
94parser.add_argument(
95 "--src-mask-filename", type=str, help="Filename to read the mask from."
96)
97parser.add_argument(
98 "--src-mask-valid-values",
99 type=int,
100 nargs="*",
101 default=[1],
102 help="Valid values for the mask",
103)
104parser.add_argument("--src-grid-name", type=str, default=None)
105
106parser.add_argument("tgt_grid_path", type=str,
107 help=(
108 "Path to the target grid file, or a shorthand: "
109 "'healpix<N>' for a HEALPix grid at zoom level N (e.g. 'healpix5'), "
110 "or 'reg[lon_from:lon_to:]<lon_n>,[lat_from:lat_to:]<lat_n>' for a "
111 "regular structured grid (e.g. 'reg360,180' or 'reg-180:180:360,-90:90:181')."
112 )
113)
114parser.add_argument(
115 "--tgt-location",
116 type=lambda loc: yac_location[loc],
117 choices=[
118 yac_location.YAC_LOC_CELL,
119 yac_location.YAC_LOC_CORNER,
120 yac_location.YAC_LOC_EDGE,
121 ],
122 default=yac_location.YAC_LOC_CELL,
123)
124parser.add_argument("--tgt-mask-var", type=str, help="Variable name of the mask")
125parser.add_argument(
126 "--tgt-mask-filename", type=str, help="Filename to read the mask from."
127)
128parser.add_argument(
129 "--tgt-mask-valid-values",
130 type=int,
131 nargs="*",
132 default=[1],
133 help="Valid values for the mask",
134)
135parser.add_argument("--tgt-grid-name", "--tgt_grid_name", type=str, default=None)
136
137parser.add_argument(
138 "interpolation_stack",
139 type=parse_interpolation,
140 nargs="+",
141 help="format: method_name[,JSON mapping]",
142)
143parser.add_argument("--output-file", type=Path, default="weights.nc")
144parser.add_argument("--output-grids-file",
145 type=Path,
146 default="grids.nc",
147 help="Write the source and target grids to this file for debugging purposes.")
148parser.add_argument(
149 "--log-level",
150 default=logging.INFO,
151 type=lambda x: getattr(logging, x),
152 help="Configure the logging level.",
153)
154
155args = parser.parse_args()
156
157logging.basicConfig(level=args.log_level)
158
159logging.info("Reading src grid")
160src_uxgrid = load_grid(args.src_grid_path, args.src_grid_name)
161src_grid = BasicGrid.from_uxgrid(args.src_grid_name or "source_grid", src_uxgrid)
162src_coords = get_coords(args.src_location, src_uxgrid, src_grid)
163if args.src_mask_var is not None:
164 src_mask_file = args.src_mask_filename or args.src_grid_path
165 with ux.open_dataset(args.src_grid_path, src_mask_file) as ds:
166 m = np.isin(ds[args.src_mask_var], args.src_mask_valid_values)
167 src_mask = src_grid.add_mask(args.src_location, m)
168else:
169 src_mask = None
170src_field = InterpField(src_coords, src_mask)
171
172logging.info("Reading tgt grid")
173tgt_uxgrid = load_grid(args.tgt_grid_path, args.tgt_grid_name)
174tgt_grid = BasicGrid.from_uxgrid(args.tgt_grid_name or "target_grid", tgt_uxgrid)
175tgt_coords = get_coords(args.tgt_location, tgt_uxgrid, tgt_grid)
176if args.tgt_mask_var is not None:
177 tgt_mask_file = args.tgt_mask_filename or args.tgt_grid_path
178 with netCDF4.Dataset(tgt_mask_file) as ds:
179 m = np.isin(ds[args.tgt_mask_var], args.tgt_mask_valid_values)
180 tgt_mask = tgt_grid.add_mask(args.tgt_location, m)
181else:
182 tgt_mask = None
183tgt_field = InterpField(tgt_coords, tgt_mask)
184
185interp_stack = InterpolationStack.from_list(args.interpolation_stack)
186
187logging.info(f"Writing grid file {args.output_grids_file}")
188if args.output_grids_file.exists():
189 logging.info(f"Overwriting existing file")
190 args.output_grids_file.unlink()
191src_grid.to_file(str(args.output_grids_file))
192tgt_grid.to_file(str(args.output_grids_file))
193
194logging.info("Computing weights")
195weights = compute_weights(interp_stack, src_field, tgt_field)
196
197logging.info("Write weights to file")
198weights.write_to_file(
199 str(args.output_file),
200 src_grid_name=args.src_grid_name or "source_grid",
201 tgt_grid_name=args.tgt_grid_name or "target_grid",
202 on_existing=yac_weight_file_on_existing.YAC_WEIGHT_FILE_OVERWRITE,
203)
load_grid(filename, gridname=None)
get_coords(loc, uxgrid, yac_grid)