Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
874 changes: 874 additions & 0 deletions docs/gallery/noaa_models/refs.ipynb

Large diffs are not rendered by default.

3,987 changes: 3,638 additions & 349 deletions docs/gallery/noaa_models/rrfs.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Herbie provides access to many numerical weather prediction models, including:
- **AI Global Ensemble Forecast System (AIGEFS)** - AI Global ensemble predictions
- **Hybrid Global Ensemble Forecast System (HGEFS)** - Hybrid Global ensemble predictions
- **National Blend of Models (NBM)** - Statistically blended forecasts
- **Rapid Refresh Forecast System (RRFS)** - Next-generation RAP/HRRR *(prototype)*
- **Rapid Refresh Forecast System (RRFS) and RRFS Ensemble Forecast System (REFS)** - Next-generation high-resolution model *(pre-implementation)*
- **Real-Time/Un-Restricted Mesoscale Analysis (RTMA/URMA)** - Gridded observations
- **Hurricane Analysis and Forecast System (HAFS)** - Tropical cyclone forecasts
- **Climate Forecast System (CFS)** - Seasonal predictions
Expand Down
2 changes: 1 addition & 1 deletion src/herbie/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ def _validate(self) -> None:
_models = {m for m in dir(model_templates) if not m.startswith("__")}
_products = set(self.PRODUCTS)

assert self.date < pd.Timestamp.utcnow().tz_localize(None), (
assert self.date < pd.Timestamp.now("UTC").tz_localize(None), (
"🔮 `date` cannot be in the future."
)
assert self.model in _models, f"`model` must be one of {_models}"
Expand Down
16 changes: 10 additions & 6 deletions src/herbie/models/refs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
RRFS Ensemble Forecast System (REFS)
"""

import warnings

HELP = r"""
Herbie(date, model='refs', ...)

fxx : int
product : {"mean", "sprd", "pmmn", "lpmm", "avrg", "prob", "eas", "ffri"}
domain : {"conus", "alaska", "hawaii", "puerto rico", "na"}

If product="natlev", then domain should be "na"
"""


Expand Down Expand Up @@ -41,20 +41,24 @@ def template(self):
self.domain = getattr(self, "domain", None) or "conus"
self.domain = domain_map.get(self.domain, self.domain)

if self.fxx==0:
warnings.warn(
"REFS does not include fxx=0, using fxx=1 instead."
)
self.fxx = 1

self.SOURCES = {
"aws": (
f"https://noaa-rrfs-ops-pds.s3.amazonaws.com/"
f"refs.{self.date:%Y%m%d/%H}/"
f"refs.{self.date:%Y%m%d/%H}/ensprod/"
f"refs.t{self.date:%H}z.{self.product}.f{self.fxx:02d}.{self.domain}.grib2"
),
"nomads": (
f"https://nomads.ncep.noaa.gov/pub/data/nccf/com/rrfs/v1.0/"
f"rrfsens.{self.date:%Y%m%d/%H}/"
f"https://nomads.ncep.noaa.gov/pub/data/nccf/com/refs/v1.0/"
f"refs.{self.date:%Y%m%d/%H}/ensprod/"
f"refs.t{self.date:%H}z.{self.product}.f{self.fxx:02d}.{self.domain}.grib2"
),

}

self.LOCALFILE = f"{self.get_remoteFileName}"

81 changes: 31 additions & 50 deletions src/herbie/models/rrfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
Herbie(date, model='rrfs', ...)

fxx : int
product : {"prs", "2dfld", "testbed", "ififip", "subh"}
product : {"prslev", "prslevnomads", "2dfld", "2dfldnomads", "subh"}
domain : {"conus", "alaska", "hawaii", "puerto rico", "na"}

If product="natlev", then domain should be "na"
"""


Expand All @@ -23,10 +22,9 @@ def template(self):

self.PRODUCTS = {
"prslev": "pressure level fields",
"natlev": "native level fields",
"prslevnomads": "pressure level fields, ensemble",
"2dfld": "2D surface/post-processed fields",
"testbed": "testbed fields",
"ififip": "icing/freezing fields",
"2dfldnomads": "2D surface/post-processed fields, ensemble",
"subh": "Subhourly grids (available with 2D fields only)"
}

Expand All @@ -45,11 +43,8 @@ def template(self):

# Format the domain parameter (default to conus)
domain_map = {"alaska": "ak", "hawaii": "hi", "puerto rico": "pr"}
if self.product == "natlev":
self.domain = "na"
else:
self.domain = getattr(self, "domain", None) or "conus"
self.domain = domain_map.get(self.domain, self.domain)
self.domain = getattr(self, "domain", None) or "conus"
self.domain = domain_map.get(self.domain, self.domain)

# Resolution depends on the domain
if self.domain in ("hi", "pr"):
Expand All @@ -59,48 +54,34 @@ def template(self):
else:
resolution = "3km"


# Ensemble member (int) vs deterministic (None/other)
self.member = getattr(self, "member", None)

self.SOURCES = {
"aws": (
f"https://noaa-rrfs-ops-pds.s3.amazonaws.com/"
f"rrfs.{self.date:%Y%m%d/%H}/"
f"rrfs.t{self.date:%H}z.{self.product}.{resolution}{extra_subh}.f{self.fxx:03d}.{self.domain}.grib2"
),
"nomads": (
f"https://nomads.ncep.noaa.gov/pub/data/nccf/com/rrfs/v1.0/"
f"rrfs.{self.date:%Y%m%d/%H}/"
f"rrfs.t{self.date:%H}z.{self.product}.{resolution}{extra_subh}.f{self.fxx:03d}.{self.domain}.grib2"
),

}
if self.member is None:
self.SOURCES = {
"aws": (
f"https://noaa-rrfs-ops-pds.s3.amazonaws.com/"
f"rrfs.{self.date:%Y%m%d/%H}/"
f"rrfs.t{self.date:%H}z.{self.product}.{resolution}{extra_subh}.f{self.fxx:03d}.{self.domain}.grib2"
),
"nomads": (
f"https://nomads.ncep.noaa.gov/pub/data/nccf/com/rrfs/v1.0/"
f"rrfs.{self.date:%Y%m%d/%H}/"
f"rrfs.t{self.date:%H}z.{self.product}.{resolution}{extra_subh}.f{self.fxx:03d}.{self.domain}.grib2"
),
}
else: # member > 1 is specified
self.SOURCES = {
"aws": (
f"https://noaa-rrfs-ops-pds.s3.amazonaws.com/"
f"rrfsens.{self.date:%Y%m%d/%H}/m{self.member:03d}/"
f"rrfs.t{self.date:%H}z.m{self.member:03d}.{self.product}.{resolution}{extra_subh}.f{self.fxx:03d}.{self.domain}.grib2"
),
"nomads": (
f"https://nomads.ncep.noaa.gov/pub/data/nccf/com/rrfs/v1.0/"
f"rrfsens.{self.date:%Y%m%d/%H}/m{self.member:03d}/"
f"rrfs.t{self.date:%H}z.m{self.member:03d}.{self.product}.{resolution}{extra_subh}.f{self.fxx:03d}.{self.domain}.grib2"
),
}

self.LOCALFILE = f"{self.get_remoteFileName}"

# prototype version -
class rrfs_old:
def template(self):
self.DESCRIPTION = "Rapid Refresh Forecast System (RRFS) (prototype)"
self.DETAILS = {
"aws product description": "https://registry.opendata.aws/noaa-rrfs/",
}
self.PRODUCTS = {
# Below are ensemble products found in ensprod/
"mean": "ensemble mean",
"avrg": "ensemble products: ???",
"eas": "ensemble products: ???",
"ffri": "ensemble products: ???",
"lpmm": "ensemble products: ???",
"pmmn": "ensemble products: ???",
"prob": "ensemble products: ???",
# Below are member products found in mem##/
"testbed.conus": "surface grids (one for each member)",
"na": "native grids (one for each member)",
}
self.SOURCES = {
"aws": f"https://noaa-rrfs-pds.s3.amazonaws.com/rrfs.{self.date:%Y%m%d/%H}/ensprod/rrfsce.t{self.date:%H}z.conus.{self.product}.f{self.fxx:02d}.grib2",
"aws-mem": f"https://noaa-rrfs-pds.s3.amazonaws.com/rrfs.{self.date:%Y%m%d/%H}/mem{self.member:02d}/rrfs.t{self.date:%H}z.mem{self.member:02d}.{self.product}f{self.fxx:03d}.grib2",
}
self.LOCALFILE = f"mem{self.member:02d}/{self.get_remoteFileName}"
31 changes: 28 additions & 3 deletions tests/test_refs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def test_refs_ffri_defaults_to_conus():
today,
model="refs",
product="ffri",
fxx=0,
fxx=1,
save_dir=save_dir,
)
assert H.domain == "conus"
Expand All @@ -42,19 +42,44 @@ def test_refs_domain_mapping(domain_in, domain_out):
model="refs",
product="mean",
domain=domain_in,
fxx=0,
fxx=1,
save_dir=save_dir,
)
assert H.domain == domain_out


def test_refs_fxx_0():
"""fxx=1 should return warning and change fxx to 1."""
with pytest.warns(UserWarning, match="REFS does not") as warn:
H = Herbie(
today,
model="refs",
product="mean",
fxx=0,
save_dir=save_dir,
)
assert H.fxx == 1


def test_refs_ffri_maps_to_conus():
"""if pfduct is ffri, domain should be set to conus."""
H = Herbie(
today,
model="refs",
product="ffri",
fxx=1,
save_dir=save_dir,
)
assert H.domain == "conus"


def test_refs_sprd_product_accepted():
"""sprd should be a valid product and default to domain='conus'."""
H = Herbie(
today,
model="refs",
product="sprd",
fxx=0,
fxx=1,
save_dir=save_dir,
)
assert H.product == "sprd"
Expand Down
61 changes: 61 additions & 0 deletions tests/test_rrfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,47 @@
save_dir = config["default"]["save_dir"] / "Herbie-Tests-Data/"


def test_rrfs():
H = Herbie(
today,
model="rrfs",
fxx=12,
save_dir=save_dir,
overwrite=True,
)

assert H.grib, "RRFS grib2 file not found"
assert H.idx, "RRFS index file not found"


def test_rrfs_ens():
H = Herbie(
today,
model="rrfs",
product='2dfldnomads',
fxx=12,
member=1,
save_dir=save_dir,
overwrite=True,
)

assert H.grib, "RRFS (ensemble) grib2 file not found"
assert H.idx, "RRFS (ensemble) index file not found"


def test_rrfs_subh():
H = Herbie(
today,
model="rrfs",
product='subh',
fxx=12,
save_dir=save_dir,
overwrite=True,
)

assert H.grib, "RRFS (subh) grib2 file not found"
assert H.idx, "RRFS (subh) index file not found"


def test_rrfs_prslev_defaults_to_conus():
"""prslev product should default domain to 'conus'."""
Expand All @@ -25,6 +66,26 @@ def test_rrfs_prslev_defaults_to_conus():
assert H.domain == "conus"


def test_rrfs_product_formatting():
"""prs product should map to, 2d map to 2dfld."""
H1 = Herbie(
today,
model="rrfs",
product="prs",
fxx=0,
save_dir=save_dir,
)
H2 = Herbie(
today,
model="rrfs",
product="2d",
fxx=0,
save_dir=save_dir,
)
assert H1.product == "prslev"
assert H2.product == "2dfld"


@pytest.mark.parametrize(
"domain_in,domain_out",
[
Expand Down
Loading