| """Variable definitions for FuXi weather forecasting models. |
| |
| Defines the C85 variable table (85 channels) used by FuXi HR: |
| - 5 pressure-level variables Γ 13 levels = 65 channels |
| - 20 surface variables |
| """ |
|
|
| from __future__ import annotations |
|
|
| from dataclasses import dataclass, field |
|
|
| |
| |
| NORM_EPSILON: float = 1e-8 |
|
|
|
|
| @dataclass(frozen=True) |
| class VariableInfo: |
| """Metadata for a single forecast variable (one channel).""" |
|
|
| short_name: str |
| long_name: str |
| level: int | None = None |
| units: str = "" |
| is_accumulated: bool = False |
| is_log_transformed: bool = False |
| channel_index: int = -1 |
|
|
| @property |
| def channel_name(self) -> str: |
| """Canonical channel name: '{var}{level}' for PL, '{var}' for SFC.""" |
| if self.level is not None: |
| return f"{self.short_name}{self.level}" |
| return self.short_name |
|
|
|
|
| |
|
|
| PRESSURE_LEVELS: list[int] = [ |
| 50, 100, 150, 200, 250, 300, 400, 500, 600, 700, 850, 925, 1000, |
| ] |
|
|
| |
|
|
| PL_VARIABLES: list[tuple[str, str, str]] = [ |
| ("z", "geopotential", "mΒ²/sΒ²"), |
| ("t", "temperature", "K"), |
| ("u", "u_component_of_wind", "m/s"), |
| ("v", "v_component_of_wind", "m/s"), |
| ("q", "specific_humidity", "kg/kg"), |
| ] |
|
|
| |
|
|
| _SFC_DEFS: list[tuple[str, str, str, bool, bool]] = [ |
| |
| |
| ("msl", "mean_sea_level_pressure", "Pa", False, False), |
| ("t2m", "2m_temperature", "K", False, False), |
| ("d2m", "2m_dewpoint_temperature", "K", False, False), |
| ("sst", "sea_surface_temperature", "K", False, False), |
| ("ws10m", "10m_wind_speed", "m/s", False, False), |
| ("ws100m", "100m_wind_speed", "m/s", False, False), |
| ("u10m", "10m_u_component_of_wind", "m/s", False, False), |
| ("v10m", "10m_v_component_of_wind", "m/s", False, False), |
| ("u100m", "100m_u_component_of_wind", "m/s", False, False), |
| ("v100m", "100m_v_component_of_wind", "m/s", False, False), |
| ("lcc", "low_cloud_cover", "0-1", False, False), |
| ("mcc", "medium_cloud_cover", "0-1", False, False), |
| ("hcc", "high_cloud_cover", "0-1", False, False), |
| ("tcc", "total_cloud_cover", "0-1", False, False), |
| ("ssr", "surface_net_solar_radiation", "J/mΒ²", True, False), |
| ("ssrd", "surface_solar_radiation_downwards", "J/mΒ²", True, False), |
| ("fdir", "total_sky_direct_solar_radiation_at_surface", "J/mΒ²", True, False), |
| ("ttr", "top_net_thermal_radiation", "J/mΒ²", True, False), |
| ("tcw", "total_column_water", "kg/mΒ²", False, False), |
| ("tp", "total_precipitation", "m", True, True), |
| ] |
|
|
|
|
| |
|
|
| def _build_c85() -> list[VariableInfo]: |
| """Construct the ordered list of 85 VariableInfo entries.""" |
| table: list[VariableInfo] = [] |
| idx = 0 |
|
|
| |
| for short, long, units in PL_VARIABLES: |
| for lev in PRESSURE_LEVELS: |
| table.append(VariableInfo( |
| short_name=short, |
| long_name=long, |
| level=lev, |
| units=units, |
| channel_index=idx, |
| )) |
| idx += 1 |
|
|
| |
| for short, long, units, accum, logt in _SFC_DEFS: |
| table.append(VariableInfo( |
| short_name=short, |
| long_name=long, |
| units=units, |
| is_accumulated=accum, |
| is_log_transformed=logt, |
| channel_index=idx, |
| )) |
| idx += 1 |
|
|
| return table |
|
|
|
|
| C85_VARIABLES: list[VariableInfo] = _build_c85() |
| """All 85 variables in channel order.""" |
|
|
| C85_CHANNEL_NAMES: list[str] = [v.channel_name for v in C85_VARIABLES] |
| """Channel names in order: ['z50', 'z100', ..., 'msl', 't2m', ..., 'tp'].""" |
|
|
| |
| _NAME_TO_VAR: dict[str, VariableInfo] = {v.channel_name: v for v in C85_VARIABLES} |
| _NAME_TO_IDX: dict[str, int] = {v.channel_name: v.channel_index for v in C85_VARIABLES} |
|
|
| |
| ACCUMULATED_CHANNELS: frozenset[str] = frozenset( |
| v.channel_name for v in C85_VARIABLES if v.is_accumulated |
| ) |
| LOG_TRANSFORMED_CHANNELS: frozenset[str] = frozenset( |
| v.channel_name for v in C85_VARIABLES if v.is_log_transformed |
| ) |
|
|
|
|
| def get_variable(channel_name: str) -> VariableInfo: |
| """Look up variable metadata by channel name. |
| |
| Raises: |
| KeyError: if *channel_name* is not in C85. |
| """ |
| return _NAME_TO_VAR[channel_name] |
|
|
|
|
| def get_channel_index(channel_name: str) -> int: |
| """Return the 0-based index of *channel_name* in C85 ordering. |
| |
| Raises: |
| KeyError: if *channel_name* is not in C85. |
| """ |
| return _NAME_TO_IDX[channel_name] |
|
|
|
|
| |
|
|
| CONST_CHANNEL_NAMES: list[str] = [ |
| "geo", |
| "lsm", |
| "cos_lat", |
| "sin_lat", |
| "cos_lon", |
| "sin_lon", |
| ] |
| N_CONST_CHANNELS: int = len(CONST_CHANNEL_NAMES) |
|
|