12"""Plot interpolation weights from YAC weight files on a map."""
19import matplotlib.pyplot
as plt
22from _utils.plotting
import (
24 plot_grid_edges_in_ax,
29 add_keybinding_legend,
34 """Load and validate YAC weight file.
37 weightfile: Path to weight file
40 Tuple of (src_grid_name, tgt_grid_name, src_loc, tgt_loc,
41 src_address, tgt_address, weights, fixed_values)
42 where fixed_values is a list of (value, dst_address_array) tuples,
43 one entry per distinct fixed value (empty list when none are present).
45 logging.info(f
"Loading weight file: {weightfile}")
46 with netCDF4.Dataset(weightfile, mode=
"r")
as wf:
47 assert(wf.version ==
"yac weight file 1.0")
48 num_weights = wf.dimensions[
"num_wgts"].size
49 logging.debug(f
"Weight file has {num_weights} weight(s)")
52 f
"Only files with num_weights==1 are supported, got {num_weights}"
55 src_grid_name = wf.src_grid_name
56 tgt_grid_name = wf.dst_grid_name
57 logging.info(f
"Source grid: {src_grid_name}")
58 logging.info(f
"Target grid: {tgt_grid_name}")
60 src_loc = netCDF4.chartostring(wf[
"src_locations"][0, :])[()].strip()
61 logging.info(f
"Source location: {src_loc}")
63 tgt_loc = netCDF4.chartostring(wf[
"dst_location"][:])[()].strip()
64 logging.info(f
"Target location: {tgt_loc}")
66 src_address = np.asarray(wf[
"src_address"])
67 tgt_address = np.asarray(wf[
"dst_address"])
69 weights = np.asarray(wf[
"remap_matrix"])
71 logging.info(f
"Total weight links: {len(src_address)}")
72 logging.debug(f
"Weight range: [{weights.min():.6f}, {weights.max():.6f}]")
75 if "fixed_values" in wf.variables:
76 fv = np.asarray(wf[
"fixed_values"])
77 n_per_fv = np.asarray(wf[
"num_dst_per_fixed_value"])
78 dst_fixed = np.asarray(wf[
"dst_address_fixed"])
80 for value, count
in zip(fv, n_per_fv):
81 fixed_values.append((float(value), dst_fixed[offset : offset + count]))
83 total = sum(c
for _, c
in [(v, len(a))
for v, a
in fixed_values])
85 f
"Fixed values: {len(fixed_values)} distinct, "
86 f
"{total} target points total"
112 """Filter interpolation weights to focus on a specific source or target index.
115 src_idx: Global ID of the source cell/vertex to filter by (or None). This is
116 the value shown as a label when pressing 's', i.e. the .gid/.vid value
118 tgt_idx: Global ID of the target cell/vertex to filter by (or None). See src_idx.
119 src_address: Array of source addresses from the weight file. Each value is
120 global_id + 1 (1-indexed global IDs, not array positions).
121 tgt_address: Array of target addresses from the weight file. Each value is
122 global_id + 1 (1-indexed global IDs, not array positions).
123 weights: Weight values
124 src_ids: Global IDs of source grid points (.gid or .vid from grid file)
125 tgt_ids: Global IDs of target grid points (.gid or .vid from grid file)
126 src_points: Source coordinates, shape (2, n)
127 tgt_points: Target coordinates, shape (2, n)
130 Tuple of (filtered_src_address, filtered_tgt_address, filtered_weights, focus_points)
132 if src_idx
is not None:
133 logging.info(f
"Filtering weights for source global ID {src_idx}")
136 weight_mask = src_address == src_idx + 1
138 fixed_global_id = src_idx
139 linked_address = tgt_address[weight_mask]
141 fixed_points = src_points
142 linked_points = tgt_points
143 filter_type =
"source"
145 logging.info(f
"Filtering weights for target global ID {tgt_idx}")
146 weight_mask = tgt_address == tgt_idx + 1
148 fixed_global_id = tgt_idx
149 linked_address = src_address[weight_mask]
151 fixed_points = tgt_points
152 linked_points = src_points
153 filter_type =
"target"
157 sorter = np.argsort(linked_ids)
158 linked_idx = sorter[np.searchsorted(linked_ids, linked_address - 1, sorter=sorter)]
161 fixed_point_idx = np.where(fixed_ids == fixed_global_id)[0]
163 if len(fixed_point_idx) == 0:
164 raise ValueError(f
"Global ID {fixed_global_id} not found in {filter_type} grid")
167 focus_points = np.hstack(
168 [fixed_points[..., fixed_point_idx], linked_points[..., linked_idx]]
172 filtered_src_address = src_address[weight_mask]
173 filtered_tgt_address = tgt_address[weight_mask]
174 filtered_weights = weights[weight_mask]
176 num_filtered = len(filtered_weights)
178 f
"Found {num_filtered} weight links for {filter_type} global ID {fixed_global_id}"
182 f
"Filtered weight range: [{filtered_weights.min():.6f}, {filtered_weights.max():.6f}]"
184 logging.debug(f
"Weight sum: {filtered_weights.sum():.6f}")
186 return filtered_src_address, filtered_tgt_address, filtered_weights, focus_points
189def plot_grid(ax, corner_coords, coords, ids, core_mask, color, label_key):
190 """Plot a grid on the given axis with specified coordinates, IDs, and styling.
192 Core cells are plotted with full opacity; halo cells (core_mask==False)
193 are plotted with a dimmed, dashed style to distinguish them visually.
196 ax: Matplotlib axis to plot on
197 corner_coords: Corner coordinates of the grid
198 coords: Coordinates of grid points
199 ids: IDs of grid points
200 core_mask: Boolean array, True for core (owned) cells, False for halo
201 color: Color for core cells
202 label_key: Key for labeling
205 f
"Plotting grid with {np.sum(core_mask)} core + "
206 f
"{np.sum(~core_mask)} halo cells in {color}"
208 plot_grid_edges_in_ax(
210 corner_coords[0, core_mask],
211 corner_coords[1, core_mask],
212 {
"zorder": 1,
"color": color},
214 if np.any(~core_mask):
215 plot_grid_edges_in_ax(
217 corner_coords[0, ~core_mask],
218 corner_coords[1, ~core_mask],
219 {
"zorder": 1,
"color": color,
"alpha": 0.3,
"linestyle":
"dashed"},
227 {
"color": color,
"visible":
False},
232if __name__ ==
"__main__":
233 parser = argparse.ArgumentParser(
234 description=
"Plot YAC interpolation weights on a map"
241 help=
"Increase verbosity (-v for INFO, -vv for DEBUG)",
248 help=
"Center of the map projection (longitude, latitude in degrees)",
250 metavar=(
"LON",
"LAT"),
256 help=
"Radius in km of the region around the center to display (default: 2000)",
264 choices=(
"10m",
"50m",
"110m"),
265 help=
"Resolution of coastlines (default: 50m). Use --coast-res without value to disable.",
270 default=
"orthographic",
271 choices=(
"orthographic",
"stereographic",
"platecarree"),
272 help=
"Map projection type (default: orthographic)",
275 idx_group = parser.add_mutually_exclusive_group()
276 idx_group.add_argument(
280 help=
"Focus on a source cell/vertex. (global index).",
282 idx_group.add_argument(
286 help=
"Focus on a target cell/vertex. (global index)",
292 help=
"Path to YAC weight file (NetCDF format)",
297 help=
"Path to a grid file (NetCDF format). The file is searched for the grid name specified in the weight file.",
303 help=
"Path to a second grid file to search for grid names. Use this when the source and target grids are stored in separate files.",
306 args = parser.parse_args()
308 log_level = [logging.WARNING, logging.INFO, logging.DEBUG][min(args.verbose, 2)]
309 logging.basicConfig(level=log_level, format=
"%(levelname)s: %(message)s")
311 logging.info(
"Starting YAC weight visualization")
312 logging.debug(f
"Weight file: {args.weightfile}")
313 logging.debug(f
"Grid files: {args.gridfile}, {args.gridfile2}")
314 logging.debug(f
"Projection: {args.projection}")
316 logging.debug(f
"Center: {args.center[0]:.2f}°E, {args.center[1]:.2f}°N")
317 logging.debug(f
"Radius: {args.radius} km")
330 logging.info(
"Loading source grid")
331 src_corners, src_points, src_ids, src_core_mask = load_grid(
332 [args.gridfile, args.gridfile2],
337 logging.info(
"Loading target grid")
338 tgt_corners, tgt_points, tgt_ids, tgt_core_mask = load_grid(
339 [args.gridfile, args.gridfile2],
346 if args.source_index
is not None or args.target_index
is not None:
359 logging.info(
"Setting up figure and map projection")
360 fig, ax = setup_figure(
361 projection=args.projection,
363 focus_points=focus_points,
365 coast_res=args.coast_res,
368 logging.info(
"Plotting source grid (green)")
369 plot_grid(ax, src_corners, src_points, src_ids, src_core_mask,
"green",
"S")
371 logging.info(
"Plotting target grid (blue)")
372 plot_grid(ax, tgt_corners, tgt_points, tgt_ids, tgt_core_mask,
"blue",
"T")
374 logging.info(f
"Plotting {len(weights)} weight connections")
387 logging.info(f
"Plotting {len(fixed_values)} fixed-value group(s)")
388 plot_fixed_values(ax, tgt_points, tgt_ids, fixed_values)
390 add_keybinding_legend(
393 (
"S",
"toggle source labels",
"green"),
394 (
"T",
"toggle target labels",
"blue"),
395 (
"W",
"toggle weight labels"),
399 logging.info(
"Opening interactive plot window")
400 logging.info(
"Press 's' to toggle source labels, 't' to toggle target labels")
filter_weights_by_index(src_idx, tgt_idx, src_address, tgt_address, weights, src_ids, tgt_ids, src_points, tgt_points)
Filter interpolation weights to focus on a specific source or target index.
plot_grid(ax, corner_coords, coords, ids, core_mask, color, label_key)
Plot a grid on the given axis with specified coordinates, IDs, and styling.
load_weight_file(weightfile)
Load and validate YAC weight file.