anemoi-datasets 0.5.19__py3-none-any.whl → 0.5.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- anemoi/datasets/_version.py +2 -2
- anemoi/datasets/commands/compare-lam.py +401 -0
- anemoi/datasets/commands/grib-index.py +114 -0
- anemoi/datasets/create/filters/pressure_level_relative_humidity_to_specific_humidity.py +3 -1
- anemoi/datasets/create/filters/pressure_level_specific_humidity_to_relative_humidity.py +3 -1
- anemoi/datasets/create/filters/wz_to_w.py +3 -2
- anemoi/datasets/create/input/action.py +2 -0
- anemoi/datasets/create/input/result.py +1 -1
- anemoi/datasets/create/sources/anemoi_dataset.py +73 -0
- anemoi/datasets/create/sources/grib.py +7 -0
- anemoi/datasets/create/sources/grib_index.py +614 -0
- anemoi/datasets/create/sources/xarray_support/__init__.py +1 -1
- anemoi/datasets/create/sources/xarray_support/fieldlist.py +2 -2
- anemoi/datasets/create/sources/xarray_support/flavour.py +6 -0
- anemoi/datasets/data/__init__.py +16 -0
- anemoi/datasets/data/complement.py +4 -1
- anemoi/datasets/data/dataset.py +14 -0
- anemoi/datasets/data/interpolate.py +76 -0
- anemoi/datasets/data/masked.py +77 -0
- anemoi/datasets/data/misc.py +159 -0
- anemoi/datasets/grids.py +8 -2
- {anemoi_datasets-0.5.19.dist-info → anemoi_datasets-0.5.20.dist-info}/METADATA +10 -4
- {anemoi_datasets-0.5.19.dist-info → anemoi_datasets-0.5.20.dist-info}/RECORD +27 -23
- {anemoi_datasets-0.5.19.dist-info → anemoi_datasets-0.5.20.dist-info}/WHEEL +0 -0
- {anemoi_datasets-0.5.19.dist-info → anemoi_datasets-0.5.20.dist-info}/entry_points.txt +0 -0
- {anemoi_datasets-0.5.19.dist-info → anemoi_datasets-0.5.20.dist-info}/licenses/LICENSE +0 -0
- {anemoi_datasets-0.5.19.dist-info → anemoi_datasets-0.5.20.dist-info}/top_level.txt +0 -0
anemoi/datasets/_version.py
CHANGED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
+
#
|
|
3
|
+
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
+
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
+
#
|
|
6
|
+
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
+
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
+
# nor does it submit to any jurisdiction.
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import math
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
from anemoi.datasets import open_dataset
|
|
15
|
+
|
|
16
|
+
from . import Command
|
|
17
|
+
|
|
18
|
+
RADIUS_EARTH_KM = 6371.0 # Earth's radius in kilometers
|
|
19
|
+
|
|
20
|
+
LOG = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HTML_Writer:
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.html_content = """
|
|
26
|
+
<!DOCTYPE html>
|
|
27
|
+
<html>
|
|
28
|
+
<head>
|
|
29
|
+
<style>
|
|
30
|
+
table {
|
|
31
|
+
border-collapse: collapse;
|
|
32
|
+
width: 100%;
|
|
33
|
+
}
|
|
34
|
+
th, td {
|
|
35
|
+
border: 1px solid black;
|
|
36
|
+
padding: 8px;
|
|
37
|
+
text-align: center;
|
|
38
|
+
}
|
|
39
|
+
th {
|
|
40
|
+
background-color: #e2e2e2;
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
</head>
|
|
44
|
+
<body>
|
|
45
|
+
<table>
|
|
46
|
+
<thead>
|
|
47
|
+
<tr>
|
|
48
|
+
<th>Variable</th>
|
|
49
|
+
<th>Global Mean</th>
|
|
50
|
+
<th>LAM Mean</th>
|
|
51
|
+
<th>Mean Diff (%)</th>
|
|
52
|
+
<th>Global Std</th>
|
|
53
|
+
<th>LAM Std</th>
|
|
54
|
+
<th>Std Diff (%)</th>
|
|
55
|
+
<th>Global Max</th>
|
|
56
|
+
<th>LAM Max</th>
|
|
57
|
+
<th>Max Diff (%)</th>
|
|
58
|
+
<th>Global Min</th>
|
|
59
|
+
<th>LAM Min</th>
|
|
60
|
+
<th>Min Diff (%)</th>
|
|
61
|
+
</tr>
|
|
62
|
+
</thead>
|
|
63
|
+
<tbody>
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def update_table(
|
|
67
|
+
self,
|
|
68
|
+
v1,
|
|
69
|
+
global_mean,
|
|
70
|
+
lam_mean,
|
|
71
|
+
mean_diff,
|
|
72
|
+
global_std,
|
|
73
|
+
lam_std,
|
|
74
|
+
std_diff,
|
|
75
|
+
global_max,
|
|
76
|
+
lam_max,
|
|
77
|
+
max_diff,
|
|
78
|
+
global_min,
|
|
79
|
+
lam_min,
|
|
80
|
+
min_diff,
|
|
81
|
+
):
|
|
82
|
+
|
|
83
|
+
# Determine inline style for HTML
|
|
84
|
+
mean_bg_color = "background-color: #d4edda;" if abs(mean_diff) < 20 else "background-color: #f8d7da;"
|
|
85
|
+
std_bg_color = "background-color: #d4edda;" if abs(std_diff) < 20 else "background-color: #f8d7da;"
|
|
86
|
+
max_bg_color = "background-color: #d4edda;" if abs(max_diff) < 20 else "background-color: #f8d7da;"
|
|
87
|
+
min_bg_color = "background-color: #d4edda;" if abs(min_diff) < 20 else "background-color: #f8d7da;"
|
|
88
|
+
|
|
89
|
+
# Add a row to the HTML table with inline styles
|
|
90
|
+
self.html_content += f"""
|
|
91
|
+
<tr>
|
|
92
|
+
<td style="background-color: #f2f2f2;">{v1}</td>
|
|
93
|
+
<td>{global_mean}</td>
|
|
94
|
+
<td>{lam_mean}</td>
|
|
95
|
+
<td style="{mean_bg_color}">{mean_diff}%</td>
|
|
96
|
+
<td>{global_std}</td>
|
|
97
|
+
<td>{lam_std}</td>
|
|
98
|
+
<td style="{std_bg_color}">{std_diff}%</td>
|
|
99
|
+
<td>{global_max}</td>
|
|
100
|
+
<td>{lam_max}</td>
|
|
101
|
+
<td style="{max_bg_color}">{max_diff}%</td>
|
|
102
|
+
<td>{global_min}</td>
|
|
103
|
+
<td>{lam_min}</td>
|
|
104
|
+
<td style="{min_bg_color}">{min_diff}%</td>
|
|
105
|
+
</tr>
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def save_table(self, save_path="stats_table.html"):
|
|
109
|
+
# Close the HTML tags
|
|
110
|
+
self.html_content += """
|
|
111
|
+
</tbody>
|
|
112
|
+
</table>
|
|
113
|
+
</body>
|
|
114
|
+
</html>
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
# Save the HTML content to a file
|
|
118
|
+
with open(save_path, "w") as f:
|
|
119
|
+
f.write(self.html_content)
|
|
120
|
+
|
|
121
|
+
LOG.info(f"\nHTML table saved to: {save_path}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def plot_coordinates_on_map(lats, lons):
|
|
125
|
+
import cartopy.crs as ccrs
|
|
126
|
+
import cartopy.feature as cfeature
|
|
127
|
+
import matplotlib.pyplot as plt
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
Plots the given latitude and longitude coordinates on a map using Cartopy and Matplotlib.
|
|
131
|
+
|
|
132
|
+
Parameters:
|
|
133
|
+
- lats: List of latitudes
|
|
134
|
+
- lons: List of longitudes
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
if len(lats) != len(lons):
|
|
138
|
+
raise ValueError("The length of latitude and longitude lists must be the same.")
|
|
139
|
+
|
|
140
|
+
# Create a figure and axis using the PlateCarree projection
|
|
141
|
+
|
|
142
|
+
# Define source (PlateCarree) and target (LambertConformal) projections
|
|
143
|
+
target_proj = ccrs.LambertConformal(central_latitude=0, central_longitude=10, standard_parallels=[63.3, 63.3])
|
|
144
|
+
|
|
145
|
+
# Create a figure and axis
|
|
146
|
+
fig, ax = plt.subplots(figsize=(14, 12), subplot_kw={"projection": target_proj})
|
|
147
|
+
|
|
148
|
+
# Set the extent of the map based on the transformed coordinates
|
|
149
|
+
margin = 10
|
|
150
|
+
ax.set_extent(
|
|
151
|
+
[min(lons) - margin, max(lons) + margin, min(lats) - margin, max(lats) + margin], crs=ccrs.PlateCarree()
|
|
152
|
+
)
|
|
153
|
+
# ax.set_extent([-25, 45, 30, 75], crs=ccrs.PlateCarree())
|
|
154
|
+
|
|
155
|
+
# Add map features
|
|
156
|
+
ax.add_feature(cfeature.LAND)
|
|
157
|
+
ax.add_feature(cfeature.OCEAN)
|
|
158
|
+
ax.add_feature(cfeature.COASTLINE.with_scale("50m"), zorder=1, alpha=0.8)
|
|
159
|
+
ax.add_feature(cfeature.BORDERS.with_scale("50m"), linestyle=":", zorder=1)
|
|
160
|
+
|
|
161
|
+
# Plot transformed coordinates
|
|
162
|
+
ax.scatter(lons, lats, color="blue", s=1, edgecolor="b", transform=ccrs.PlateCarree(), alpha=0.3)
|
|
163
|
+
ax.set_title("Latitude and Longitude")
|
|
164
|
+
ax.title.set_size(20)
|
|
165
|
+
|
|
166
|
+
# Show the plot
|
|
167
|
+
return fig
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def haversine(lat1, lon1, lat2, lon2):
|
|
171
|
+
"""Calculate the great-circle distance between two points on the Earth's surface using the Haversine formula."""
|
|
172
|
+
dlat = math.radians(lat2 - lat1)
|
|
173
|
+
dlon = math.radians(lon2 - lon1)
|
|
174
|
+
a = math.sin(dlat / 2) ** 2 + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlon / 2) ** 2
|
|
175
|
+
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
|
176
|
+
return RADIUS_EARTH_KM * c
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def rectangle_area_km2(lat1, lon1, lat2, lon2):
|
|
180
|
+
"""Calculate the area of a rectangle given the coordinates of the top left and bottom right corners in
|
|
181
|
+
latitude and longitude.
|
|
182
|
+
|
|
183
|
+
Parameters:
|
|
184
|
+
lat1, lon1 - Latitude and longitude of the top-left corner.
|
|
185
|
+
lat2, lon2 - Latitude and longitude of the bottom-right corner.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Area in square kilometers (km^2).
|
|
189
|
+
"""
|
|
190
|
+
# Calculate the height (difference in latitude)
|
|
191
|
+
height_km = haversine(lat1, lon1, lat2, lon1)
|
|
192
|
+
|
|
193
|
+
# Calculate the width (difference in longitude)
|
|
194
|
+
width_km = haversine(lat1, lon1, lat1, lon2)
|
|
195
|
+
|
|
196
|
+
# Area of the rectangle
|
|
197
|
+
area_km2 = height_km * width_km
|
|
198
|
+
return area_km2
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def check_order(vars_1, vars_2):
|
|
202
|
+
for v1, v2 in zip(vars_1, vars_2):
|
|
203
|
+
if v1 != v2 and ((v1 in vars_2) and (v2 in vars_1)):
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def compute_wighted_diff(s1, s2, round_ndigits):
|
|
210
|
+
return round((s2 - s1) * 100 / s2, ndigits=round_ndigits)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class CompareLAM(Command):
|
|
214
|
+
"""Compare statistic of two datasets. \
|
|
215
|
+
This command compares the statistics of each variable in two datasets ONLY in the overlapping area between the two. \
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
def add_arguments(self, command_parser):
|
|
219
|
+
command_parser.add_argument("dataset1", help="Path of the global dataset or the largest dataset.")
|
|
220
|
+
command_parser.add_argument("dataset2", help="Path of the LAM dataset or the smallest dataset.")
|
|
221
|
+
command_parser.add_argument(
|
|
222
|
+
"-D", "--number-of-dates", type=int, default=10, help="Number of datapoints (in time) to compare over."
|
|
223
|
+
)
|
|
224
|
+
command_parser.add_argument("-O", "--outpath", default="./", help="Path to output folder.")
|
|
225
|
+
command_parser.add_argument("-R", "--number-of-digits", type=int, default=4, help="Number of digits to keep.")
|
|
226
|
+
command_parser.add_argument(
|
|
227
|
+
"--selected-vars",
|
|
228
|
+
nargs="+",
|
|
229
|
+
default=["10u", "10v", "2d", "2t"],
|
|
230
|
+
help="List of selected variables to use in the script.",
|
|
231
|
+
)
|
|
232
|
+
command_parser.add_argument(
|
|
233
|
+
"--save-plots", action="store_true", help="Toggle to save a picture of the data grid."
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def run(self, args):
|
|
237
|
+
import matplotlib.pyplot as plt
|
|
238
|
+
import numpy as np
|
|
239
|
+
from prettytable import PrettyTable
|
|
240
|
+
from termcolor import colored # For coloring text in the terminal
|
|
241
|
+
|
|
242
|
+
# Unpack args
|
|
243
|
+
date_idx = args.number_of_dates
|
|
244
|
+
round_ndigits = args.number_of_digits
|
|
245
|
+
selected_vars = args.selected_vars
|
|
246
|
+
global_name = args.dataset1
|
|
247
|
+
lam_name = args.dataset2
|
|
248
|
+
date_idx = 10 # "all" or specific index to stop at
|
|
249
|
+
name = f"{global_name}-{lam_name}_{date_idx}"
|
|
250
|
+
save_path = os.path.join(args.outpath, f"comparison_table_{name}.html")
|
|
251
|
+
|
|
252
|
+
# Open LAM dataset
|
|
253
|
+
lam_dataset = open_dataset(lam_name, select=selected_vars)
|
|
254
|
+
lam_vars = list(lam_dataset.variables)
|
|
255
|
+
lam_num_grid_points = lam_dataset[0, 0].shape[1]
|
|
256
|
+
lam_area = rectangle_area_km2(
|
|
257
|
+
max(lam_dataset.latitudes),
|
|
258
|
+
max(lam_dataset.longitudes),
|
|
259
|
+
min(lam_dataset.latitudes),
|
|
260
|
+
min(lam_dataset.longitudes),
|
|
261
|
+
)
|
|
262
|
+
l_coords = (
|
|
263
|
+
max(lam_dataset.latitudes),
|
|
264
|
+
min(lam_dataset.longitudes),
|
|
265
|
+
min(lam_dataset.latitudes),
|
|
266
|
+
max(lam_dataset.longitudes),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if args.save_plots:
|
|
270
|
+
_ = plot_coordinates_on_map(lam_dataset.latitudes, lam_dataset.longitudes)
|
|
271
|
+
plt.savefig(os.path.join(args.outpath, "lam_dataset.png"))
|
|
272
|
+
|
|
273
|
+
LOG.info(f"Dataset {lam_name}, has {lam_num_grid_points} grid points. \n")
|
|
274
|
+
LOG.info("LAM (north, west, south, east): ", l_coords)
|
|
275
|
+
LOG.info(f"Point every: {math.sqrt(lam_area / lam_num_grid_points)} km")
|
|
276
|
+
|
|
277
|
+
# Open global dataset and cut it
|
|
278
|
+
lam_start = lam_dataset.dates[0]
|
|
279
|
+
lam_end = lam_dataset.dates[-1]
|
|
280
|
+
global_dataset = open_dataset(global_name, start=lam_start, end=lam_end, area=l_coords, select=selected_vars)
|
|
281
|
+
global_vars = list(global_dataset.variables)
|
|
282
|
+
global_num_grid_points = global_dataset[0, 0].shape[1]
|
|
283
|
+
global_area = rectangle_area_km2(
|
|
284
|
+
max(global_dataset.latitudes),
|
|
285
|
+
max(global_dataset.longitudes),
|
|
286
|
+
min(global_dataset.latitudes),
|
|
287
|
+
min(global_dataset.longitudes),
|
|
288
|
+
)
|
|
289
|
+
g_coords = (
|
|
290
|
+
max(global_dataset.latitudes),
|
|
291
|
+
min(global_dataset.longitudes),
|
|
292
|
+
min(global_dataset.latitudes),
|
|
293
|
+
max(global_dataset.longitudes),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if args.save_plots:
|
|
297
|
+
_ = plot_coordinates_on_map(global_dataset.latitudes, global_dataset.longitudes)
|
|
298
|
+
plt.savefig(os.path.join(args.outpath, "global_dataset.png"))
|
|
299
|
+
|
|
300
|
+
LOG.info(f"Dataset {global_name}, has {global_num_grid_points} grid points. \n")
|
|
301
|
+
LOG.info("Global-lam cut (north, west, south, east): ", g_coords)
|
|
302
|
+
LOG.info(f"Point every: {math.sqrt(global_area / global_num_grid_points)} km")
|
|
303
|
+
|
|
304
|
+
# Check variable ordering
|
|
305
|
+
same_order = check_order(global_vars, lam_vars)
|
|
306
|
+
LOG.info(f"Lam dataset has the same order of variables as the global dataset: {same_order}")
|
|
307
|
+
|
|
308
|
+
LOG.info("\nComparing statistics..")
|
|
309
|
+
table = PrettyTable()
|
|
310
|
+
table.field_names = [
|
|
311
|
+
"Variable",
|
|
312
|
+
"Global Mean",
|
|
313
|
+
"LAM Mean",
|
|
314
|
+
"Mean Diff (%)",
|
|
315
|
+
"Global Std",
|
|
316
|
+
"LAM Std",
|
|
317
|
+
"Std Diff (%)",
|
|
318
|
+
"Global Max",
|
|
319
|
+
"LAM Max",
|
|
320
|
+
"Max Diff (%)",
|
|
321
|
+
"Global Min",
|
|
322
|
+
"LAM Min",
|
|
323
|
+
"Min Diff (%)",
|
|
324
|
+
]
|
|
325
|
+
|
|
326
|
+
# Create a styled HTML table
|
|
327
|
+
html_writer = HTML_Writer()
|
|
328
|
+
|
|
329
|
+
for v1, v2 in zip(global_vars, lam_vars):
|
|
330
|
+
assert v1 == v2
|
|
331
|
+
idx = global_vars.index(v1)
|
|
332
|
+
|
|
333
|
+
if date_idx == "all":
|
|
334
|
+
lam_mean = lam_dataset.statistics["mean"][idx]
|
|
335
|
+
lam_std = lam_dataset.statistics["stdev"][idx]
|
|
336
|
+
lam_max = lam_dataset.statistics["max"][idx]
|
|
337
|
+
lam_min = lam_dataset.statistics["min"][idx]
|
|
338
|
+
global_mean = global_dataset.statistics["mean"][idx]
|
|
339
|
+
global_std = global_dataset.statistics["stdev"][idx]
|
|
340
|
+
global_max = global_dataset.statistics["max"][idx]
|
|
341
|
+
global_min = global_dataset.statistics["min"][idx]
|
|
342
|
+
|
|
343
|
+
else:
|
|
344
|
+
lam_mean = np.nanmean(lam_dataset[:date_idx], axis=(0, 3))[idx][0]
|
|
345
|
+
lam_std = np.nanstd(lam_dataset[:date_idx], axis=(0, 3))[idx][0]
|
|
346
|
+
lam_max = np.nanmax(lam_dataset[:date_idx], axis=(0, 3))[idx][0]
|
|
347
|
+
lam_min = np.nanmin(lam_dataset[:date_idx], axis=(0, 3))[idx][0]
|
|
348
|
+
global_mean = np.nanmean(global_dataset[:date_idx], axis=(0, 3))[idx][0]
|
|
349
|
+
global_std = np.nanstd(global_dataset[:date_idx], axis=(0, 3))[idx][0]
|
|
350
|
+
global_max = np.nanmax(global_dataset[:date_idx], axis=(0, 3))[idx][0]
|
|
351
|
+
global_min = np.nanmin(global_dataset[:date_idx], axis=(0, 3))[idx][0]
|
|
352
|
+
|
|
353
|
+
mean_diff = compute_wighted_diff(lam_mean, global_mean, round_ndigits)
|
|
354
|
+
std_diff = compute_wighted_diff(lam_std, global_std, round_ndigits)
|
|
355
|
+
max_diff = compute_wighted_diff(lam_max, global_max, round_ndigits)
|
|
356
|
+
min_diff = compute_wighted_diff(lam_min, global_min, round_ndigits)
|
|
357
|
+
|
|
358
|
+
mean_color = "red" if abs(mean_diff) >= 20 else "green"
|
|
359
|
+
std_color = "red" if abs(std_diff) >= 20 else "green"
|
|
360
|
+
max_color = "red" if abs(max_diff) >= 20 else "green"
|
|
361
|
+
min_color = "red" if abs(min_diff) >= 20 else "green"
|
|
362
|
+
|
|
363
|
+
table.add_row(
|
|
364
|
+
[
|
|
365
|
+
v1,
|
|
366
|
+
round(global_mean, ndigits=round_ndigits),
|
|
367
|
+
round(lam_mean, ndigits=round_ndigits),
|
|
368
|
+
colored(f"{mean_diff}%", mean_color),
|
|
369
|
+
round(global_std, ndigits=round_ndigits),
|
|
370
|
+
round(lam_std, ndigits=round_ndigits),
|
|
371
|
+
colored(f"{std_diff}%", std_color),
|
|
372
|
+
round(global_max, ndigits=round_ndigits),
|
|
373
|
+
round(lam_max, ndigits=round_ndigits),
|
|
374
|
+
colored(f"{max_diff}%", max_color),
|
|
375
|
+
round(global_min, ndigits=round_ndigits),
|
|
376
|
+
round(lam_min, ndigits=round_ndigits),
|
|
377
|
+
colored(f"{min_diff}%", min_color),
|
|
378
|
+
]
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
html_writer.update_table(
|
|
382
|
+
v1,
|
|
383
|
+
global_mean,
|
|
384
|
+
lam_mean,
|
|
385
|
+
mean_diff,
|
|
386
|
+
global_std,
|
|
387
|
+
lam_std,
|
|
388
|
+
std_diff,
|
|
389
|
+
global_max,
|
|
390
|
+
lam_max,
|
|
391
|
+
max_diff,
|
|
392
|
+
global_min,
|
|
393
|
+
lam_min,
|
|
394
|
+
min_diff,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
html_writer.save_table(save_path)
|
|
398
|
+
print(table)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
command = CompareLAM
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
+
#
|
|
3
|
+
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
+
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
+
#
|
|
6
|
+
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
+
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
+
# nor does it submit to any jurisdiction.
|
|
9
|
+
|
|
10
|
+
import fnmatch
|
|
11
|
+
import os
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import tqdm
|
|
15
|
+
|
|
16
|
+
from . import Command
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class GribIndexCmd(Command):
|
|
20
|
+
internal = True
|
|
21
|
+
timestamp = True
|
|
22
|
+
|
|
23
|
+
def add_arguments(self, command_parser: Any) -> None:
|
|
24
|
+
"""Add arguments to the command parser.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
command_parser : Any
|
|
29
|
+
The command parser to which arguments are added.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from anemoi.datasets.create.sources.grib_index import KEYS
|
|
33
|
+
|
|
34
|
+
command_parser.add_argument(
|
|
35
|
+
"--index",
|
|
36
|
+
help="Path to the index file to create or update",
|
|
37
|
+
required=True,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
command_parser.add_argument(
|
|
41
|
+
"--overwrite",
|
|
42
|
+
action="store_true",
|
|
43
|
+
help="Overwrite the index file if it exists (default is to update)",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
command_parser.add_argument(
|
|
47
|
+
"--match",
|
|
48
|
+
help="Give a glob pattern to match files (default: *.grib)",
|
|
49
|
+
default="*.grib",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
command_parser.add_argument(
|
|
53
|
+
"--keys",
|
|
54
|
+
help="GRIB keys to add to the index, separated by commas. If the list starts with a +, the keys are added to default list.",
|
|
55
|
+
default=",".join(KEYS),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
command_parser.add_argument(
|
|
59
|
+
"--flavour",
|
|
60
|
+
help="GRIB flavour file (yaml or json)",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
command_parser.add_argument("paths", nargs="+", help="Paths to scan")
|
|
64
|
+
|
|
65
|
+
def run(self, args: Any) -> None:
|
|
66
|
+
"""Execute the scan command.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
args : Any
|
|
71
|
+
The arguments passed to the command.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def match(path: str) -> bool:
|
|
75
|
+
"""Check if a path matches the given pattern.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
path : str
|
|
80
|
+
The path to check.
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
bool
|
|
85
|
+
True if the path matches, False otherwise.
|
|
86
|
+
"""
|
|
87
|
+
return fnmatch.fnmatch(path, args.match)
|
|
88
|
+
|
|
89
|
+
from anemoi.datasets.create.sources.grib_index import GribIndex
|
|
90
|
+
|
|
91
|
+
index = GribIndex(
|
|
92
|
+
args.index,
|
|
93
|
+
keys=args.keys,
|
|
94
|
+
update=True,
|
|
95
|
+
overwrite=args.overwrite,
|
|
96
|
+
flavour=args.flavour,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
paths = []
|
|
100
|
+
for path in args.paths:
|
|
101
|
+
if os.path.isfile(path):
|
|
102
|
+
paths.append(path)
|
|
103
|
+
else:
|
|
104
|
+
for root, _, files in os.walk(path):
|
|
105
|
+
for file in files:
|
|
106
|
+
full = os.path.join(root, file)
|
|
107
|
+
paths.append(full)
|
|
108
|
+
|
|
109
|
+
for path in tqdm.tqdm(paths, leave=False):
|
|
110
|
+
if match(path):
|
|
111
|
+
index.add_grib_file(path)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
command = GribIndexCmd
|
|
@@ -71,7 +71,9 @@ def execute(context: Any, input: ekd.FieldList, t: str, rh: str, q: str = "q") -
|
|
|
71
71
|
|
|
72
72
|
t_pl = values[t].to_numpy(flatten=True)
|
|
73
73
|
rh_pl = values[rh].to_numpy(flatten=True)
|
|
74
|
-
pressure =
|
|
74
|
+
pressure = next(
|
|
75
|
+
float(v) * 100 for k, v in keys if k in ["level", "levelist"]
|
|
76
|
+
) # Looks first for "level" then "levelist" value
|
|
75
77
|
# print(f"Handling fields for pressure level {pressure}...")
|
|
76
78
|
|
|
77
79
|
# actual conversion from rh --> q_v
|
|
@@ -72,7 +72,9 @@ def execute(context: Any, input: ekd.FieldList, t: str, q: str, rh: str = "r") -
|
|
|
72
72
|
|
|
73
73
|
t_pl = values[t].to_numpy(flatten=True)
|
|
74
74
|
q_pl = values[q].to_numpy(flatten=True)
|
|
75
|
-
pressure =
|
|
75
|
+
pressure = next(
|
|
76
|
+
float(v) * 100 for k, v in keys if k in ["level", "levelist"]
|
|
77
|
+
) # Looks first for "level" then "levelist" value
|
|
76
78
|
# print(f"Handling fields for pressure level {pressure}...")
|
|
77
79
|
|
|
78
80
|
# actual conversion from rh --> q_v
|
|
@@ -66,8 +66,9 @@ def execute(context: Any, input: ekd.FieldList, wz: str, t: str, w: str = "w") -
|
|
|
66
66
|
|
|
67
67
|
wz_pl = values[wz].to_numpy(flatten=True)
|
|
68
68
|
t_pl = values[t].to_numpy(flatten=True)
|
|
69
|
-
pressure =
|
|
70
|
-
|
|
69
|
+
pressure = next(
|
|
70
|
+
float(v) * 100 for k, v in keys if k in ["level", "levelist"]
|
|
71
|
+
) # Looks first for "level" then "levelist" value
|
|
71
72
|
w_pl = wz_to_w(wz_pl, t_pl, pressure)
|
|
72
73
|
result.append(new_field_from_numpy(values[wz], w_pl, param=w))
|
|
73
74
|
|
|
@@ -239,11 +239,13 @@ def action_factory(config: Dict[str, Any], context: ActionContext, action_path:
|
|
|
239
239
|
|
|
240
240
|
cls = {
|
|
241
241
|
"data_sources": DataSourcesAction,
|
|
242
|
+
"data-sources": DataSourcesAction,
|
|
242
243
|
"concat": ConcatAction,
|
|
243
244
|
"join": JoinAction,
|
|
244
245
|
"pipe": PipeAction,
|
|
245
246
|
"function": FunctionAction,
|
|
246
247
|
"repeated_dates": RepeatedDatesAction,
|
|
248
|
+
"repeated-dates": RepeatedDatesAction,
|
|
247
249
|
}.get(key)
|
|
248
250
|
|
|
249
251
|
if cls is None:
|
|
@@ -215,7 +215,7 @@ def _fields_metatata(variables: Tuple[str, ...], cube: Any) -> Dict[str, Any]:
|
|
|
215
215
|
result[k] = dict(mars=v) if v else {}
|
|
216
216
|
result[k].update(other[k])
|
|
217
217
|
result[k].update(KNOWN.get(k, {}))
|
|
218
|
-
assert result[k], k
|
|
218
|
+
# assert result[k], k
|
|
219
219
|
|
|
220
220
|
assert i + 1 == len(variables), (i + 1, len(variables))
|
|
221
221
|
return result
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# (C) Copyright 2025 Anemoi contributors.
|
|
2
|
+
#
|
|
3
|
+
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
+
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
+
#
|
|
6
|
+
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
+
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
+
# nor does it submit to any jurisdiction.
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from .legacy import legacy_source
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@legacy_source(__file__)
|
|
16
|
+
def execute(context, dates, params=None, **kwargs):
|
|
17
|
+
import earthkit.data as ekd
|
|
18
|
+
|
|
19
|
+
from anemoi.datasets import open_dataset
|
|
20
|
+
|
|
21
|
+
ds = open_dataset(**kwargs)
|
|
22
|
+
# dates_to_index = {date: i for i, date in enumerate(ds.dates)}
|
|
23
|
+
|
|
24
|
+
indices = []
|
|
25
|
+
for date in dates:
|
|
26
|
+
idx = np.where(ds.dates == date)[0]
|
|
27
|
+
if len(idx) == 0:
|
|
28
|
+
continue
|
|
29
|
+
indices.append((int(idx[0]), date))
|
|
30
|
+
|
|
31
|
+
vars = ds.variables
|
|
32
|
+
if params is None:
|
|
33
|
+
params = vars
|
|
34
|
+
|
|
35
|
+
if not isinstance(params, (list, tuple, set)):
|
|
36
|
+
params = [params]
|
|
37
|
+
|
|
38
|
+
params = set(params)
|
|
39
|
+
results = []
|
|
40
|
+
|
|
41
|
+
ensemble = ds.shape[2] > 1
|
|
42
|
+
latitudes = ds.latitudes
|
|
43
|
+
longitudes = ds.longitudes
|
|
44
|
+
|
|
45
|
+
for idx, date in indices:
|
|
46
|
+
|
|
47
|
+
metadata = dict(valid_datetime=date, latitudes=latitudes, longitudes=longitudes)
|
|
48
|
+
|
|
49
|
+
for j, y in enumerate(ds[idx]):
|
|
50
|
+
|
|
51
|
+
param = vars[j]
|
|
52
|
+
if param not in params:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# metadata['name'] = param
|
|
56
|
+
# metadata['param_level'] = param
|
|
57
|
+
metadata["param"] = param
|
|
58
|
+
|
|
59
|
+
for k, e in enumerate(y):
|
|
60
|
+
if ensemble:
|
|
61
|
+
metadata["number"] = k + 1
|
|
62
|
+
|
|
63
|
+
metadata["values"] = e
|
|
64
|
+
|
|
65
|
+
results.append(metadata.copy())
|
|
66
|
+
|
|
67
|
+
print(results[0].keys())
|
|
68
|
+
|
|
69
|
+
# "list-of-dicts" does support resolution
|
|
70
|
+
results = ekd.from_source("list-of-dicts", results)
|
|
71
|
+
|
|
72
|
+
# return new_fieldlist_from_list([new_field_from_latitudes_longitudes(x, latitudes, longitudes) for x in results])
|
|
73
|
+
return results
|