| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """Defines AbstractModel API, standard implementations and helper functions.""" |
| from __future__ import annotations |
|
|
| import collections |
| import dataclasses |
| import functools |
| from typing import Any, Callable, Dict, Optional, Sequence, Union |
| from dinosaur import coordinate_systems |
| from dinosaur import layer_coordinates |
| from dinosaur import scales |
| from dinosaur import sigma_coordinates |
| from dinosaur import spherical_harmonic |
| from dinosaur import time_integration |
| from dinosaur import typing |
| from dinosaur import xarray_utils |
| import gin |
| import haiku as hk |
| import jax.numpy as jnp |
|
|
| from model.legacy import correctors |
| from model.legacy import decoders |
| from model.legacy import embeddings |
| from model.legacy import encoders |
| from model.legacy import equations |
| from model.legacy import features |
| from model.legacy import filters |
| from model.legacy import forcings |
| from model.legacy import gin_utils |
| from model.legacy import layers |
| from model.legacy import mappings |
| from model.legacy import model_utils |
| from model.legacy import physics_specifications |
| from model.legacy import steps |
| from model.legacy import stochastic |
| from model.legacy import towers |
| from model.legacy import transforms |
| import numpy as np |
| import xarray |
| |
|
|
|
|
| DEFAULT_REFERENCE_TEMPERATURE = 288 |
| DEFAULT_REFERENCE_DATETIME_STR = '1979-01-01T00' |
|
|
| Array = typing.Array |
| AuxFeatures = typing.AuxFeatures |
| DataState = typing.DataState |
| PyTreeState = typing.PyTreeState |
| ModelState = typing.ModelState |
| ForcingData = typing.ForcingData |
| Forcing = typing.Forcing |
| Numeric = typing.Numeric |
| QuantityOrStr = Union[str, scales.Quantity] |
| |
| |
|
|
| |
| |
| |
|
|
| |
| data_to_xarray = gin.external_configurable( |
| xarray_utils.data_to_xarray, 'data_to_xarray' |
| ) |
| |
| primitive_eq_to_xarray = gin.external_configurable( |
| xarray_utils.data_to_xarray, 'primitive_eq_to_xarray' |
| ) |
| data_to_xarray_with_renaming = gin.external_configurable( |
| xarray_utils.data_to_xarray_with_renaming, 'data_to_xarray_with_renaming' |
| ) |
| dynamic_covariate_data_to_xarray = gin.external_configurable( |
| xarray_utils.dynamic_covariate_data_to_xarray, |
| 'dynamic_covariate_data_to_xarray', |
| ) |
|
|
| |
| xarray_to_shallow_water = gin.external_configurable( |
| xarray_utils.xarray_to_shallow_water_eq_data, 'xarray_to_shallow_water' |
| ) |
| xarray_to_primitive_eq = gin.external_configurable( |
| xarray_utils.xarray_to_primitive_eq_data, 'xarray_to_primitive_eq' |
| ) |
| xarray_to_primitive_eq_with_time = gin.external_configurable( |
| xarray_utils.xarray_to_primitive_equations_with_time_data, |
| 'xarray_to_primitive_eq_with_time', |
| ) |
| xarray_to_weatherbench_data = gin.external_configurable( |
| xarray_utils.xarray_to_weatherbench_data, 'xarray_to_weatherbench_data' |
| ) |
| xarray_to_data_with_renaming = gin.external_configurable( |
| xarray_utils.xarray_to_data_with_renaming, 'xarray_to_data_with_renaming' |
| ) |
| xarray_to_dynamic_covariate_data = gin.external_configurable( |
| xarray_utils.xarray_to_dynamic_covariate_data, |
| 'xarray_to_dynamic_covariate_data', |
| ) |
| xarray_to_state_and_dynamic_covariate_data = gin.external_configurable( |
| xarray_utils.xarray_to_state_and_dynamic_covariate_data, |
| 'xarray_to_state_and_dynamic_covariate_data', |
| ) |
| coordinate_system_from_dataset = gin.external_configurable( |
| xarray_utils.coordinate_system_from_dataset, |
| 'coordinate_system_from_dataset', |
| allowlist=['truncation', 'spherical_harmonics_impl'], |
| ) |
|
|
| |
| Grid = gin.external_configurable( |
| spherical_harmonic.Grid, denylist=['spmd_mesh'] |
| ) |
| GridWithWavenumbers = gin.external_configurable( |
| spherical_harmonic.Grid.with_wavenumbers, 'GridWithWavenumbers' |
| ) |
| GridT21 = gin.external_configurable(spherical_harmonic.Grid.T21, 'GridT21') |
| GridT31 = gin.external_configurable(spherical_harmonic.Grid.T31, 'GridT31') |
| GridT42 = gin.external_configurable(spherical_harmonic.Grid.T42, 'GridT42') |
| GridT85 = gin.external_configurable(spherical_harmonic.Grid.T85, 'GridT85') |
| GridT106 = gin.external_configurable(spherical_harmonic.Grid.T106, 'GridT106') |
| GridT119 = gin.external_configurable(spherical_harmonic.Grid.T119, 'GridT119') |
| GridT170 = gin.external_configurable(spherical_harmonic.Grid.T170, 'GridT170') |
| GridT213 = gin.external_configurable(spherical_harmonic.Grid.T213, 'GridT213') |
| GridTL31 = gin.external_configurable(spherical_harmonic.Grid.TL31, 'GridTL31') |
| GridTL63 = gin.external_configurable(spherical_harmonic.Grid.TL63, 'GridTL63') |
| GridTL95 = gin.external_configurable(spherical_harmonic.Grid.TL95, 'GridTL95') |
| GridTL127 = gin.external_configurable( |
| spherical_harmonic.Grid.TL127, 'GridTL127' |
| ) |
| GridTL159 = gin.external_configurable( |
| spherical_harmonic.Grid.TL159, 'GridTL159' |
| ) |
| GridTL179 = gin.external_configurable( |
| spherical_harmonic.Grid.TL179, 'GridTL179' |
| ) |
| GridTL255 = gin.external_configurable( |
| spherical_harmonic.Grid.TL255, 'GridTL255' |
| ) |
| RealSphericalHarmonics = gin.external_configurable( |
| spherical_harmonic.RealSphericalHarmonics, |
| ) |
| RealSphericalHarmonicsWithZeroImag = gin.external_configurable( |
| spherical_harmonic.RealSphericalHarmonicsWithZeroImag, |
| denylist=['spmd_mesh'], |
| ) |
| LayerCoordinates = gin.external_configurable(layer_coordinates.LayerCoordinates) |
| SigmaCoordinates = gin.external_configurable(sigma_coordinates.SigmaCoordinates) |
| SigmaCoordinatesEquidistant = gin.external_configurable( |
| sigma_coordinates.SigmaCoordinates.equidistant, |
| 'SigmaCoordinatesEquidistant', |
| ) |
| CoordinateSystem = gin.external_configurable( |
| coordinate_systems.CoordinateSystem, denylist=['spmd_mesh'] |
| ) |
|
|
| |
| centered_vertical_advection = gin.external_configurable( |
| sigma_coordinates.centered_vertical_advection |
| ) |
| upwind_vertical_advection = gin.external_configurable( |
| sigma_coordinates.upwind_vertical_advection |
| ) |
|
|
|
|
| @dataclasses.dataclass(frozen=True) |
| class ModelSpecs(collections.abc.Mapping): |
| """Specification of model configuration. |
| |
| Attributes: |
| coords: horizontal and vertical grid data. |
| dt: nondimensionalized model time step. |
| physics_specs: physical constants and definition of custom units. |
| aux_features: additional static data. |
| """ |
|
|
| coords: coordinate_systems.CoordinateSystem |
| dt: float |
| physics_specs: Any |
| aux_features: typing.AuxFeatures |
|
|
| def __len__(self): |
| return len(dataclasses.fields(self)) |
|
|
| def __iter__(self): |
| return iter(f.name for f in dataclasses.fields(self)) |
|
|
| def __getitem__(self, key): |
| return getattr(self, key) |
|
|
|
|
| @gin.configurable( |
| allowlist=( |
| 'model_time_step', |
| 'custom_coords', |
| 'reference_temperature', |
| 'reference_datetime_str', |
| ) |
| ) |
| def get_model_specs( |
| data_coords: coordinate_systems.CoordinateSystem, |
| physics_specs: Any, |
| aux_features: typing.AuxFeatures, |
| model_time_step: Optional[Union[float, QuantityOrStr]] = None, |
| custom_coords: Optional[coordinate_systems.CoordinateSystem] = None, |
| reference_temperature: Optional[float | Sequence] = None, |
| reference_datetime_str: Optional[str] = None, |
| ) -> ModelSpecs: |
| """Returns specifications for a WhirlModel configuration. |
| |
| Provides gin hooks, and in some cases defaults, for model specification |
| formerly encoded in aux_features. |
| |
| Args: |
| data_coords: coordinate system in which states are represented in the data. |
| physics_specs: physical constants and definition of custom units. |
| aux_features: auxiliary features that come with the dataset. |
| model_time_step: duration of the outer time-step in our model, i.e., the |
| time by which the state is advanced in a single model.advance call. |
| custom_coords: optional coordinate system to be used by the model instead of |
| data_coords. |
| reference_temperature: reference temperature to use for sigma coordinates. |
| Must be None if already defined in aux_features. Default value of 288 |
| used if None and also not in aux_features. |
| reference_datetime_str: reference datetime for which nondimensionalized time |
| is set to 0. Must be None if already defined in aux_features. Default |
| value of '1979-01-01T00' used if None and also not in aux_features. |
| |
| Returns: |
| Configured specification of coordinate system, time-step, physical constants |
| and units, and aux_features and for our hybrid ML/physics model. |
| """ |
| if model_time_step is None: |
| raise ValueError('must provide model_time_step or outer_time_step') |
|
|
| if custom_coords is None: |
| coords = data_coords |
| else: |
| coords = dataclasses.replace(custom_coords, spmd_mesh=data_coords.spmd_mesh) |
|
|
| if aux_features.get(xarray_utils.REF_TEMP_KEY) is None: |
| if reference_temperature is None: |
| ones = np.ones(coords.vertical.layers, np.float32) |
| ref_temps = DEFAULT_REFERENCE_TEMPERATURE * ones |
| aux_features[xarray_utils.REF_TEMP_KEY] = ref_temps |
| else: |
| ones = np.ones(coords.vertical.layers, np.float32) |
| ref_temps = np.asarray(reference_temperature) |
| if ref_temps.ndim == 1 and ref_temps.shape[0] != coords.vertical.layers: |
| raise ValueError( |
| '`ref_temps` must be a scalar or a sequence with ' |
| f'{coords.vertical.layers=} elements, got {ref_temps.shape=}' |
| ) |
| ref_temps = ref_temps * ones |
| aux_features[xarray_utils.REF_TEMP_KEY] = ref_temps |
| else: |
| if reference_temperature is not None: |
| raise ValueError( |
| 'reference temperature already specified in aux_features' |
| ) |
|
|
| if aux_features.get(xarray_utils.REFERENCE_DATETIME_KEY) is None: |
| if reference_datetime_str is None: |
| reference_datetime = np.datetime64(DEFAULT_REFERENCE_DATETIME_STR) |
| aux_features[xarray_utils.REFERENCE_DATETIME_KEY] = reference_datetime |
| else: |
| reference_datetime = np.datetime64(reference_datetime_str) |
| aux_features[xarray_utils.REFERENCE_DATETIME_KEY] = reference_datetime |
| else: |
| if reference_datetime_str is not None: |
| raise ValueError('reference datetime already specified in aux_data') |
|
|
| if isinstance(model_time_step, (str, scales.Quantity)): |
| dt = physics_specs.nondimensionalize(scales.Quantity(model_time_step)) |
| else: |
| dt = model_time_step |
|
|
| return ModelSpecs( |
| coords=coords, |
| dt=dt, |
| physics_specs=physics_specs, |
| aux_features=aux_features, |
| ) |
|
|
|
|
| def _identity(x): |
| return x |
|
|
|
|
| class DynamicalSystem(hk.Module): |
| """Abstract class for modeling dynamical systems.""" |
|
|
| def __init__( |
| self, |
| coords: coordinate_systems.CoordinateSystem, |
| dt: float, |
| physics_specs: Any, |
| aux_features: Dict[str, Any], |
| input_coords: coordinate_systems.CoordinateSystem, |
| output_coords: coordinate_systems.CoordinateSystem, |
| name: Optional[str] = None, |
| ): |
| super().__init__(name=name) |
| self.coords = coords |
| self.dt = dt |
| self.physics_specs = physics_specs |
| self.aux_features = aux_features |
| self.input_coords = input_coords |
| self.output_coords = output_coords |
|
|
| def encode(self, x: PyTreeState, forcing: Forcing) -> PyTreeState: |
| """Encodes input trajectory `x` with `forcing` to the model state.""" |
| raise NotImplementedError('Model subclass did not define encode') |
|
|
| def decode(self, x: PyTreeState, forcing: Forcing) -> PyTreeState: |
| """Decodes a model state `x` with `forcing` to a data representation.""" |
| raise NotImplementedError('Model subclass did not define decode') |
|
|
| def advance(self, x: PyTreeState, forcing: Forcing) -> PyTreeState: |
| """Returns a model state `x` with `forcing` advanced by `self.dt`.""" |
| raise NotImplementedError('Model subclass did not define advance') |
|
|
| def forcing_fn(self, forcing_data: ForcingData, sim_time: Numeric) -> Forcing: |
| """Returns forcing at sim_time, possibly using `forcing_data`.""" |
| raise NotImplementedError('Model subclass did not define forcing_fn') |
|
|
| def trajectory( |
| self, |
| x, |
| outer_steps: int, |
| inner_steps: int = 1, |
| *, |
| forcing_data: ForcingData, |
| start_with_input: bool = False, |
| post_process_fn: Callable = _identity, |
| ): |
| """Returns a final model state and trajectory.""" |
|
|
| def step_fn(x: PyTreeState) -> PyTreeState: |
| |
| if isinstance(x, typing.ModelState): |
| sim_time = getattr(x.state, 'sim_time', None) |
| else: |
| sim_time = getattr(x, 'sim_time', None) |
| forcing = self.forcing_fn(forcing_data, sim_time) |
| x, forcing = self.coords.with_dycore_sharding((x, forcing)) |
| y = self.advance(x, forcing) |
| y = self.coords.with_dycore_sharding(y) |
| return y |
|
|
| return trajectory_from_step( |
| step_fn, |
| outer_steps, |
| inner_steps, |
| start_with_input=start_with_input, |
| post_process_fn=post_process_fn, |
| )(x) |
|
|
|
|
| @gin.configurable |
| class ModularStepModel(DynamicalSystem): |
| """Dynamical model based on independent encoder/decoder/step components.""" |
|
|
| def __init__( |
| self, |
| coords: coordinate_systems.CoordinateSystem, |
| dt: float, |
| physics_specs: Any, |
| aux_features: Dict[str, Any], |
| input_coords: coordinate_systems.CoordinateSystem, |
| output_coords: coordinate_systems.CoordinateSystem, |
| advance_module=gin.REQUIRED, |
| encoder_module=gin.REQUIRED, |
| decoder_module=gin.REQUIRED, |
| forcing_module=forcings.NoForcing, |
| name: Optional[str] = None, |
| ): |
| super().__init__( |
| coords, |
| dt, |
| physics_specs, |
| aux_features, |
| input_coords, |
| output_coords, |
| name=name, |
| ) |
| self.advance_fn = advance_module(coords, dt, physics_specs, aux_features) |
| self.encoder_fn = encoder_module( |
| coords, dt, physics_specs, aux_features, input_coords |
| ) |
| self.decoder_fn = decoder_module( |
| coords, dt, physics_specs, aux_features, output_coords |
| ) |
| self.forcing_fn = forcing_module(coords, dt, physics_specs, aux_features) |
|
|
| def encode(self, x: PyTreeState, forcing: Forcing) -> PyTreeState: |
| return self.encoder_fn(x, forcing) |
|
|
| def decode(self, x: PyTreeState, forcing: Forcing) -> PyTreeState: |
| return self.decoder_fn(x, forcing) |
|
|
| def advance(self, x: PyTreeState, forcing: Forcing) -> PyTreeState: |
| return self.advance_fn(x, forcing) |
|
|
|
|
| @gin.configurable |
| class StochasticModularStepModel(DynamicalSystem): |
| """Dynamical model with modular components and stochasticity. |
| |
| This instance of DynamicalSystem works with ModelState |
| representation of the model state. The `advance_module` initializes a |
| RandomnessModule. This must be compatible with ModelState. |
| Since randomness initialization might depend on the timestep at which it is |
| evolved, RandomnessModule module is initialized with `num_substeps`. |
| """ |
|
|
| def __init__( |
| self, |
| coords: coordinate_systems.CoordinateSystem, |
| dt: float, |
| physics_specs: Any, |
| aux_features: Dict[str, Any], |
| input_coords: coordinate_systems.CoordinateSystem, |
| output_coords: coordinate_systems.CoordinateSystem, |
| advance_module=gin.REQUIRED, |
| encoder_module=gin.REQUIRED, |
| decoder_module=gin.REQUIRED, |
| forcing_module=forcings.NoForcing, |
| name: Optional[str] = None, |
| ): |
| super().__init__( |
| coords, |
| dt, |
| physics_specs, |
| aux_features, |
| input_coords, |
| output_coords, |
| name=name, |
| ) |
| self.advance_fn = advance_module(coords, dt, physics_specs, aux_features) |
| self.encoder_fn = encoder_module( |
| coords, dt, physics_specs, aux_features, input_coords |
| ) |
| self.decoder_fn = decoder_module( |
| coords, dt, physics_specs, aux_features, output_coords |
| ) |
| self.forcing_fn = forcing_module(coords, dt, physics_specs, aux_features) |
|
|
| def encode( |
| self, |
| x: DataState, |
| forcing: Forcing, |
| ) -> ModelState: |
| """Encodes model state and creates a new perturbation.""" |
| model_state = self.encoder_fn(x, forcing=forcing) |
| |
| |
| return self.advance_fn.finalize_state(model_state, forcing) |
|
|
| def decode(self, x: ModelState, forcing: Forcing) -> typing.Pytree: |
| """Returns model state with perturbation component removed.""" |
| |
| |
| return self.decoder_fn(x, forcing=forcing) |
|
|
| def advance( |
| self, |
| x: ModelState, |
| forcing: Forcing, |
| ) -> ModelState: |
| """Advances model state.""" |
| return self.advance_fn(x, forcing) |
|
|
|
|
| @gin.configurable( |
| allowlist=( |
| 'checkpoint_step', |
| 'checkpoint_multistep', |
| 'checkpoint_post_process', |
| ) |
| ) |
| def trajectory_from_step( |
| step_fn: Callable, |
| outer_steps: int, |
| inner_steps: int, |
| *, |
| start_with_input: bool, |
| post_process_fn: Callable, |
| checkpoint_step: bool = True, |
| checkpoint_multistep: bool = False, |
| checkpoint_post_process: bool = True, |
| ) -> Callable: |
| """Returns a function that accumulates repeated applications of `step_fn`. |
| |
| Compute a trajectory by repeatedly calling `step_fn()` |
| `outer_steps * inner_steps` times. |
| |
| Args: |
| step_fn: function that takes a state and returns state after one time step. |
| outer_steps: number of steps to save in the generated trajectory. |
| inner_steps: number of repeated calls to step_fn() between saved steps. |
| start_with_input: if True, output the trajectory at steps [0, ..., steps-1] |
| instead of steps [1, ..., steps]. |
| post_process_fn: function to apply to trajectory outputs. |
| checkpoint_step: whether to use `jax.checkpoint` on `step_fn`. |
| checkpoint_multistep: weather to use `jax.checkpoint` on `step_fn` repeated |
| steps between outputting observations used in the loss. Multi-step |
| checkpointing is off by default; turn it on to trade off ~25% increased |
| computed for ~25% less memory usage. |
| checkpoint_post_process: whether to use `jax.checkpoint` on |
| `post_process_fn`. `checkpoint_post_process` is a no-op if multi-step |
| checkpointing is enabled. |
| |
| Returns: |
| A function that takes an initial state and returns a tuple consisting of: |
| (1) the final frame of the trajectory. |
| (2) trajectory of length `outer_steps` representing time evolution. |
| """ |
| if checkpoint_step: |
| step_fn = hk.remat(step_fn) |
|
|
| if checkpoint_post_process: |
| post_process_fn = hk.remat(post_process_fn) |
|
|
| if checkpoint_multistep: |
|
|
| def outer_scan_fn(f, init, xs, length=None): |
| return hk.scan(hk.remat(f), init, xs, length=length) |
|
|
| else: |
| outer_scan_fn = hk.scan |
|
|
| return time_integration.trajectory_from_step( |
| step_fn, |
| outer_steps, |
| inner_steps, |
| start_with_input=start_with_input, |
| post_process_fn=post_process_fn, |
| inner_scan_fn=hk.scan, |
| outer_scan_fn=outer_scan_fn, |
| ) |
|
|
|
|
| @gin.configurable(allowlist=('model_cls', 'to_xarray_fn', 'from_xarray_fn')) |
| class WhirlModel: |
| """Class that holds a Haiku model class and xarray conversion methods.""" |
|
|
| def __init__( |
| self, |
| coords: coordinate_systems.CoordinateSystem, |
| dt: float, |
| physics_specs: Any, |
| aux_features: Optional[AuxFeatures] = None, |
| input_coords: Optional[coordinate_systems.CoordinateSystem] = None, |
| output_coords: Optional[coordinate_systems.CoordinateSystem] = None, |
| model_cls: Callable[[], DynamicalSystem] = gin.REQUIRED, |
| to_xarray_fn: Optional[Callable[..., xarray.Dataset]] = None, |
| from_xarray_fn: Optional[Callable[..., DataState]] = None, |
| ): |
| """Constructs pre-defined model functions and holds conversion functions. |
| |
| Args: |
| coords: horizontal and vertical descritization. |
| dt: time step of the model. |
| physics_specs: object describing the scales and physical constants. |
| aux_features: dictionary holding static features that the model may use. |
| input_coords: horizontal and vertical descritization of the input data. if |
| `None`, uses `coords`. Default `None. |
| output_coords: horizontal and vertical descritization for the output data. |
| if `None`, uses `coords`. Default `None. |
| model_cls: model Haiku class that implements encode/advance/decode fns. |
| to_xarray_fn: function that converts decoded data slices to xarray. |
| from_xarray_fn: function that extracts data slices from xarray. |
| """ |
| if aux_features is None: |
| aux_features = {} |
| if input_coords is None: |
| input_coords = coords |
| if output_coords is None: |
| output_coords = coords |
| self._coords = coords |
| self._data_coords = input_coords |
| specs = ModelSpecs(coords, dt, physics_specs, aux_features) |
| model_cls = functools.partial( |
| model_cls, |
| coords=coords, |
| dt=dt, |
| physics_specs=physics_specs, |
| aux_features=aux_features, |
| input_coords=input_coords, |
| output_coords=output_coords, |
| ) |
|
|
| def forcing_fwd(forcing_data, sim_time): |
| return model_cls().forcing_fn(forcing_data, sim_time) |
|
|
| forcing_fn = hk.transform(forcing_fwd).apply |
| encode_fwd = lambda x, forcing: model_cls().encode(x, forcing) |
| encode_fn = hk.transform(encode_fwd).apply |
| decode_fwd = lambda x, forcing: model_cls().decode(x, forcing) |
| decode_fn = hk.transform(decode_fwd).apply |
| advance_fwd = lambda x, forcing: model_cls().advance(x, forcing) |
| advance_fn = hk.transform(advance_fwd).apply |
| if to_xarray_fn is not None: |
| to_xarray_fn = functools.partial(to_xarray_fn, coords=output_coords) |
| self.forcing_fn = forcing_fn |
| self.encode_fn = encode_fn |
| self.decode_fn = decode_fn |
| self.advance_fn = advance_fn |
| self.specs = specs |
| self.model_cls = model_cls |
| self.to_xarray_fn = to_xarray_fn |
| self.from_xarray_fn = from_xarray_fn |
|
|
| @property |
| def coords(self) -> coordinate_systems.CoordinateSystem: |
| return self._coords |
|
|
| @property |
| def data_coords(self) -> coordinate_systems.CoordinateSystem: |
| return self._data_coords |
|
|
| def init_params( |
| self, |
| rng: Array, |
| input_trajectory: typing.DataState, |
| forcing_data: ForcingData, |
| ) -> typing.Params: |
| """Returns model parameters by initializing encode/advance/decode fn.""" |
|
|
| def fwd(x): |
| model = self.model_cls() |
| decode = model_utils.with_forcing( |
| model.decode, model.forcing_fn, forcing_data |
| ) |
| advance = model_utils.with_forcing( |
| model.advance, model.forcing_fn, forcing_data |
| ) |
| encode = model_utils.with_forcing( |
| model.encode, model.forcing_fn, forcing_data |
| ) |
| return decode(advance(encode(x))) |
|
|
| hk_model = hk.transform(fwd) |
| return hk_model.init(rng, input_trajectory) |
|
|
|
|
| def get_whirl_model( |
| data_ds: xarray.Dataset, |
| model_config_str: str, |
| additional_gin_bindings: Optional[list[str]] = None, |
| ) -> WhirlModel: |
| """Returns a configured WhirlModel.""" |
| if additional_gin_bindings is None: |
| additional_gin_bindings = [] |
|
|
| try: |
| data_aux_features = xarray_utils.aux_features_from_xarray(data_ds) |
| except KeyError: |
| data_aux_features = {} |
|
|
| if 'physics_config_str' in data_ds.attrs: |
| physics_config_str = data_ds.attrs['physics_config_str'] |
| else: |
| physics_config_str = '' |
|
|
| gin.enter_interactive_mode() |
| gin.clear_config() |
| gin_utils.parse_gin_config( |
| physics_config_str, |
| model_config_str, |
| override_physics_configs_from_data=True, |
| gin_bindings=additional_gin_bindings, |
| ) |
|
|
| data_coords = coordinate_system_from_dataset(data_ds) |
| physics_specs = physics_specifications.get_physics_specs() |
| model_specs = get_model_specs(data_coords, physics_specs, data_aux_features) |
| return WhirlModel( |
| coords=model_specs.coords, |
| dt=model_specs.dt, |
| physics_specs=model_specs.physics_specs, |
| aux_features=model_specs.aux_features, |
| input_coords=data_coords, |
| output_coords=data_coords, |
| ) |
|
|
|
|
| _ECMWF_CUTOFFS = { |
| |
| |
| |
| 'low_cutoffs': (0.05, 0.1), |
| |
| 'high_cutoffs': (0.86, 0.965), |
| } |
|
|
|
|
| def _piecewise_squasher( |
| sigma: Array, |
| low_cutoffs: Sequence[float], |
| high_cutoffs: Sequence[float], |
| ) -> Array: |
| """Piecewise linear values used to "squash" values by sigma level. |
| |
| See function χ definition at: http://screen/5V3jzU7ZFA4vVJP |
| |
| Args: |
| sigma: 1-D array of values for sigma levels. Should be in [0, 1]. |
| low_cutoffs: σ=low_cutoffs[0] is when χ starts linearly increasing from 0. |
| σ=low_cutoffs[1] is when χ levels out at 1 |
| high_cutoffs: σ=high_cutoffs[0] is when χ starts linearly decreasing from 1. |
| σ=high_cutoffs[1] is when χ reaches 0. |
| |
| Returns: |
| Values χ of shape `sigma.shape + (1, 1)` that should be multiplied by |
| arrays of shape (n_levels, K, L) to "squash" high/low σ values. |
| """ |
| if sigma.ndim != 1: |
| raise ValueError(f'{sigma.shape=} but should have been a 1-D array') |
| if len(low_cutoffs) != 2: |
| raise ValueError(f'{len(low_cutoffs)=} but should have been 2.') |
| if len(high_cutoffs) != 2: |
| raise ValueError(f'{len(high_cutoffs)=} but should have been 2.') |
|
|
| low_func = (sigma - low_cutoffs[0]) / (low_cutoffs[1] - low_cutoffs[0]) |
| high_func = (high_cutoffs[1] - sigma) / (high_cutoffs[1] - high_cutoffs[0]) |
|
|
| |
| |
| |
| lower_bound = jnp.minimum(1.0, jnp.minimum(low_func, high_func)) |
| return jnp.maximum(0.0, lower_bound)[:, jnp.newaxis, jnp.newaxis] |
|
|