Skip to content

Commit f3b22ec

Browse files
authored
Merge pull request #503 from christianversloot/feature/noaa-aiwp-models
Add new NOAA AIWP models (AIGFS, AIGEFS, HGEFS)
2 parents ad8741b + c732d7a commit f3b22ec

2 files changed

Lines changed: 145 additions & 0 deletions

File tree

src/herbie/models/gefs.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,129 @@ def template(self):
202202
}
203203
self.IDX_SUFFIX = [".idx"]
204204
self.LOCALFILE = f"{self.get_remoteFileName}"
205+
206+
207+
class aigefs:
208+
"""AI Global Ensemble Forecast System (AIGEFS).
209+
"""
210+
211+
def template(self):
212+
self.DESCRIPTION = "AI Global Ensemble Forecast System (AIGEFS)"
213+
self.DETAILS = {
214+
"NOMADS": "https://www.nco.ncep.noaa.gov/pmb/products/aigefs/",
215+
}
216+
217+
self.PRODUCTS = {
218+
"pres": "pressure fields, 0.25 degree resolution",
219+
"sfc": "surface fields, 0.25 degree resolution",
220+
}
221+
222+
if self.product is None:
223+
# Just select the first PRODUCT as default
224+
self.product = list(self.PRODUCTS)[0]
225+
226+
if self.member == 0:
227+
self.member = "mem000"
228+
elif self.member == "control":
229+
self.member = "mem000"
230+
elif isinstance(self.member, int):
231+
self.member = f"mem{self.member:02d}"
232+
233+
filedir = f"aigefs.{self.date:%Y%m%d/%H}"
234+
if self.member in ["spr", "avg"]:
235+
filepaths = {
236+
"sfc": f"{filedir}/ensstat/products/atmos/grib2/aigefs.t{self.date:%H}z.sfc.{self.member}.f{self.fxx:03d}",
237+
"pres": f"{filedir}/ensstat/products/atmos/grib2/aigefs.t{self.date:%H}z.pres.{self.member}.f{self.fxx:03d}",
238+
}
239+
else:
240+
filepaths = {
241+
"sfc": f"{filedir}/{self.member}/model/atmos/grib2/aigefs.t{self.date:%H}z.sfc.f{self.fxx:03d}",
242+
"pres": f"{filedir}/{self.member}/model/atmos/grib2/aigefs.t{self.date:%H}z.pres.f{self.fxx:03d}",
243+
}
244+
245+
valid_members = {
246+
"pres": [f"mem{i:03d}" for i in range(0, 31)] + ["control", "spr", "avg"],
247+
"sfc": [f"mem{i:03d}" for i in range(0, 31)] + ["control", "spr", "avg"],
248+
}
249+
250+
filepath = filepaths.get(self.product)
251+
if filepath is None:
252+
raise ValueError(
253+
f"product={self.product} not recognized. Must be one of {self.PRODUCTS.keys()}"
254+
)
255+
256+
_member = valid_members.get(self.product)
257+
if _member is not None and self.member not in _member:
258+
raise ValueError(
259+
f"For AIGEFS product {self.product}, member must be one of {_member}"
260+
)
261+
262+
self.SOURCES = {
263+
"nomads": f"https://nomads.ncep.noaa.gov/pub/data/nccf/com/aigefs/prod/{filepath}.grib2"
264+
}
265+
266+
self.IDX_SUFFIX = [".grib2.idx"]
267+
self.LOCALFILE = f"aigefs.t{self.date:%H}z.{self.product}.{self.member}.f{self.fxx:03d}"
268+
269+
270+
class hgefs:
271+
"""Hybrid Ensemble Forecast System (HGEFS).
272+
"""
273+
274+
def template(self):
275+
self.DESCRIPTION = "Hybrid Global Ensemble Forecast System (HGEFS)"
276+
self.DETAILS = {
277+
"NOMADS": "https://www.nco.ncep.noaa.gov/pmb/products/hgefs/",
278+
}
279+
280+
self.PRODUCTS = {
281+
"pres": "pressure fields, 0.25 degree resolution",
282+
"sfc": "surface fields, 0.25 degree resolution",
283+
}
284+
285+
if self.product is None:
286+
# Just select the first PRODUCT as default
287+
self.product = list(self.PRODUCTS)[0]
288+
289+
if self.member == 0:
290+
self.member = "mem000"
291+
elif self.member == "control":
292+
self.member = "mem000"
293+
elif isinstance(self.member, int):
294+
self.member = f"mem{self.member:02d}"
295+
296+
filedir = f"hgefs.{self.date:%Y%m%d/%H}"
297+
if self.member in ["spr", "avg"]:
298+
filepaths = {
299+
"sfc": f"{filedir}/ensstat/products/atmos/grib2/hgefs.t{self.date:%H}z.sfc.{self.member}.f{self.fxx:03d}",
300+
"pres": f"{filedir}/ensstat/products/atmos/grib2/hgefs.t{self.date:%H}z.pres.{self.member}.f{self.fxx:03d}",
301+
}
302+
else:
303+
filepaths = {
304+
"sfc": f"{filedir}/{self.member}/model/atmos/grib2/hgefs.t{self.date:%H}z.sfc.f{self.fxx:03d}",
305+
"pres": f"{filedir}/{self.member}/model/atmos/grib2/hgefs.t{self.date:%H}z.pres.f{self.fxx:03d}",
306+
}
307+
308+
valid_members = {
309+
"pres": ["spr", "avg"],
310+
"sfc": ["spr", "avg"],
311+
}
312+
313+
filepath = filepaths.get(self.product)
314+
if filepath is None:
315+
raise ValueError(
316+
f"product={self.product} not recognized. Must be one of {self.PRODUCTS.keys()}"
317+
)
318+
319+
_member = valid_members.get(self.product)
320+
if _member is not None and self.member not in _member:
321+
raise ValueError(
322+
f"For HGEFS product {self.product}, member must be one of {_member}"
323+
)
324+
325+
self.SOURCES = {
326+
"nomads": f"https://nomads.ncep.noaa.gov/pub/data/nccf/com/hgefs/prod/{filepath}.grib2"
327+
}
328+
329+
self.IDX_SUFFIX = [".grib2.idx"]
330+
self.LOCALFILE = f"hgefs.t{self.date:%H}z.{self.product}.{self.member}.f{self.fxx:03d}"

src/herbie/models/gfs.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,22 @@ def template(self):
207207
}
208208
self.IDX_SUFFIX = [".idx"]
209209
self.LOCALFILE = f"{self.get_remoteFileName}"
210+
211+
class aigfs:
212+
"""NOAA AI Global Forecast System."""
213+
214+
def template(self):
215+
self.DESCRIPTION = "NOAA AI Global Forecast System"
216+
self.DETAILS = {
217+
"nomads product description": "https://www.nco.ncep.noaa.gov/pmb/products/aigfs",
218+
}
219+
self.PRODUCTS = {
220+
"sfc": "surface fields, 0.25 degree resolution",
221+
"pres": "pressure fields, 0.25 degree resolution"
222+
}
223+
post_root = f"aigfs.{self.date:%Y%m%d/%H}/model/atmos/grib2/aigfs.t{self.date:%H}z.{self.product}.f{self.fxx:03d}"
224+
self.SOURCES = {
225+
"nomads": f"https://nomads.ncep.noaa.gov/pub/data/nccf/com/aigfs/prod/{post_root}.grib2"
226+
}
227+
self.LOCALFILE = f"{self.get_remoteFileName}"
228+
self.IDX_SUFFIX = [".idx"]

0 commit comments

Comments
 (0)