| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """Modules that predict an embedding from the model state.""" |
| from typing import Any, Optional |
| from dinosaur import coordinate_systems |
| from dinosaur import pytree_utils |
| from dinosaur import scales |
| from dinosaur import typing |
| from dinosaur import xarray_utils |
| import gin |
| import haiku as hk |
| import jax |
| import jax.numpy as jnp |
| from model.legacy import features |
| from model.legacy import mappings |
| from model.legacy import transforms |
|
|
| EmbeddingFn = typing.EmbeddingFn |
| EmbeddingModule = typing.EmbeddingModule |
| Forcing = typing.Forcing |
| TransformModule = typing.TransformModule |
|
|
| units = scales.units |
|
|
|
|
| @gin.register |
| class ModalToNodalEmbedding(hk.Module): |
| """Embedding that expects modal state input and returns nodal output.""" |
|
|
| def __init__( |
| self, |
| coords: coordinate_systems.CoordinateSystem, |
| dt: float, |
| physics_specs: Any, |
| aux_features: typing.AuxFeatures, |
| output_shapes: typing.Pytree, |
| modal_to_nodal_features_module: features.FeaturesModule, |
| nodal_mapping_module: mappings.MappingModule, |
| output_transform_module: TransformModule = transforms.IdentityTransform, |
| name: Optional[str] = None, |
| ): |
| super().__init__(name=name) |
| self.coords = coords |
| self.output_shapes = output_shapes |
| self.modal_to_nodal_features_fn = modal_to_nodal_features_module( |
| coords, dt, physics_specs, aux_features |
| ) |
| self.nodal_mapping_module = nodal_mapping_module |
| self.output_transform_fn = output_transform_module( |
| coords, dt, physics_specs, aux_features |
| ) |
|
|
| def __call__( |
| self, |
| state: typing.Pytree, |
| memory: Optional[typing.Pytree] = None, |
| diagnostics: Optional[typing.Pytree] = None, |
| randomness: Optional[typing.Pytree] = None, |
| forcing: Optional[typing.Forcing] = None, |
| ) -> typing.Pytree: |
| """Returns the embedding output on nodal locations.""" |
| net = self.nodal_mapping_module(self.output_shapes) |
| |
| |
| state, _ = pytree_utils.as_dict(state) |
| nodal_inputs = self.modal_to_nodal_features_fn( |
| state, memory, diagnostics, randomness, forcing |
| ) |
| nodal_outputs = self.output_transform_fn(net(nodal_inputs)) |
| return nodal_outputs |
|
|
|
|
| |
| @gin.register |
| class NodalSurfaceModelEmbedding(hk.Module): |
| """Embedding to represent a nodal space surface model.""" |
|
|
| def __init__( |
| self, |
| coords: coordinate_systems.CoordinateSystem, |
| dt: float, |
| physics_specs: Any, |
| aux_features: typing.AuxFeatures, |
| output_shapes: typing.Pytree, |
| static_vars_ds_path: str, |
| land_embedding: Optional[EmbeddingModule] = None, |
| sea_embedding: Optional[EmbeddingModule] = None, |
| sea_ice_embedding: Optional[EmbeddingModule] = None, |
| snow_embedding: Optional[EmbeddingModule] = None, |
| output_transform_module: TransformModule = transforms.IdentityTransform, |
| name: Optional[str] = None, |
| ): |
| super().__init__(name=name) |
| self.coords = coords |
| self.output_shapes = output_shapes |
|
|
| |
| self.feature_axis = -3 |
| param_init = hk.initializers.TruncatedNormal() |
| output_size = sum([x[self.feature_axis] |
| for x in jax.tree_util.tree_leaves(output_shapes)]) |
| param_shape = (output_size, 1, 1) |
| surface_nodal_shape = self.coords.surface_nodal_shape |
|
|
| if land_embedding is not None: |
| self.land_embedding_fn = land_embedding( |
| coords=coords, |
| dt=dt, |
| physics_specs=physics_specs, |
| aux_features=aux_features, |
| output_shapes=output_shapes, |
| ) |
| else: |
| self.land_parameters = hk.get_parameter( |
| 'land_params', param_shape, |
| jnp.float32, init=param_init) |
| def land_embedding_fn(state, memory, randomness, forcing): |
| del state, memory, randomness, forcing |
| outputs = self.land_parameters * jnp.ones(surface_nodal_shape) |
| return pytree_utils.unpack_to_pytree( |
| outputs, self.output_shapes, self.feature_axis |
| ) |
| self.land_embedding_fn = land_embedding_fn |
|
|
| if sea_embedding is not None: |
| self.sea_embedding_fn = sea_embedding( |
| coords=coords, |
| dt=dt, |
| physics_specs=physics_specs, |
| aux_features=aux_features, |
| output_shapes=output_shapes, |
| ) |
| else: |
| self.sea_parameters = hk.get_parameter( |
| 'sea_params', param_shape, |
| jnp.float32, init=param_init) |
| def sea_embedding_fn(state, memory, randomness, forcing): |
| del state, memory, randomness, forcing |
| outputs = self.sea_parameters * jnp.ones(surface_nodal_shape) |
| return pytree_utils.unpack_to_pytree( |
| outputs, self.output_shapes, self.feature_axis |
| ) |
| self.sea_embedding_fn = sea_embedding_fn |
|
|
| if sea_ice_embedding is not None: |
| self.sea_ice_embedding_fn = sea_ice_embedding( |
| coords=coords, |
| dt=dt, |
| physics_specs=physics_specs, |
| aux_features=aux_features, |
| output_shapes=output_shapes, |
| ) |
| else: |
| self.sea_ice_parameters = hk.get_parameter( |
| 'sea_ice_params', param_shape, |
| jnp.float32, init=param_init) |
| def sea_ice_embedding_fn(state, memory, randomness, forcing): |
| del state, memory, randomness, forcing |
| outputs = self.sea_ice_parameters * jnp.ones(surface_nodal_shape) |
| return pytree_utils.unpack_to_pytree( |
| outputs, self.output_shapes, self.feature_axis |
| ) |
| self.sea_ice_embedding_fn = sea_ice_embedding_fn |
|
|
| if snow_embedding is not None: |
| self.snow_embedding_fn = snow_embedding( |
| coords=coords, |
| dt=dt, |
| physics_specs=physics_specs, |
| aux_features=aux_features, |
| output_shapes=output_shapes, |
| ) |
| else: |
| self.snow_parameters = hk.get_parameter( |
| 'snow_params', param_shape, |
| jnp.float32, init=param_init) |
| def snow_embedding_fn(state, memory, randomness, forcing): |
| del state, memory, randomness, forcing |
| outputs = self.snow_parameters * jnp.ones(surface_nodal_shape) |
| return pytree_utils.unpack_to_pytree( |
| outputs, self.output_shapes, self.feature_axis |
| ) |
| self.snow_embedding_fn = snow_embedding_fn |
|
|
| self.output_transform_fn = output_transform_module( |
| coords, dt, physics_specs, aux_features |
| ) |
|
|
| ds = xarray_utils.ds_from_path_or_aux(static_vars_ds_path, aux_features) |
| self.land_sea_mask = xarray_utils.nodal_land_sea_mask_from_ds(ds) |
|
|
| |
| |
| |
| self.snow_cover_threshold = physics_specs.nondimensionalize(1 * units.meter) |
|
|
| def __call__( |
| self, |
| state: typing.Pytree, |
| memory: Optional[typing.Pytree] = None, |
| diagnostics: Optional[typing.Pytree] = None, |
| randomness: Optional[typing.Pytree] = None, |
| forcing: Optional[typing.Forcing] = None, |
| ) -> typing.Pytree: |
| """Returns the embedding output on nodal locations.""" |
| land_outputs = self.land_embedding_fn( |
| state, memory, diagnostics, randomness, forcing) |
| sea_outputs = self.sea_embedding_fn( |
| state, memory, diagnostics, randomness, forcing) |
| sea_ice_outputs = self.sea_ice_embedding_fn( |
| state, memory, diagnostics, randomness, forcing |
| ) |
| snow_outputs = self.snow_embedding_fn( |
| state, memory, diagnostics, randomness, forcing) |
|
|
| |
| land_fraction = self.land_sea_mask |
| sea_fraction = 1 - land_fraction |
| sea_ice_fraction = forcing[xarray_utils.SEA_ICE_COVER] |
| snow_fraction = forcing[xarray_utils.SNOW_DEPTH] > self.snow_cover_threshold |
|
|
| |
| snow_weight = snow_fraction * land_fraction |
| land_weight = (1 - snow_fraction) * land_fraction |
| sea_ice_weight = sea_ice_fraction * sea_fraction |
| sea_weight = (1 - sea_ice_fraction) * sea_fraction |
|
|
| def tree_scale(a, x): |
| |
| return jax.tree_util.tree_map(lambda y: a * y, x) |
|
|
| surface_outputs = jax.tree_util.tree_map( |
| lambda a, b, c, d: a + b + c + d, |
| tree_scale(land_weight, land_outputs), |
| tree_scale(sea_weight, sea_outputs), |
| tree_scale(sea_ice_weight, sea_ice_outputs), |
| tree_scale(snow_weight, snow_outputs), |
| ) |
|
|
| return self.output_transform_fn(surface_outputs) |
|
|
|
|
| @gin.register |
| class NodalLandSeaIceEmbedding(hk.Module): |
| """Embedding to represent a nodal land/sea/sea-ice surface.""" |
|
|
| def __init__( |
| self, |
| coords: coordinate_systems.CoordinateSystem, |
| dt: float, |
| physics_specs: Any, |
| aux_features: typing.AuxFeatures, |
| output_shapes: typing.Pytree, |
| static_vars_ds_path: str, |
| land_embedding: Optional[EmbeddingModule] = None, |
| sea_embedding: Optional[EmbeddingModule] = None, |
| sea_ice_embedding: Optional[EmbeddingModule] = None, |
| output_transform_module: TransformModule = transforms.IdentityTransform, |
| name: Optional[str] = None, |
| ): |
| super().__init__(name=name) |
| self.coords = coords |
| self.output_shapes = output_shapes |
|
|
| |
| self.feature_axis = -3 |
| surface_nodal_shape = self.coords.surface_nodal_shape |
| param_init = hk.initializers.TruncatedNormal() |
| output_size = sum([x[self.feature_axis] |
| for x in jax.tree_util.tree_leaves(output_shapes)]) |
| uniform_param_shape = (output_size, 1, 1) |
| |
| |
|
|
| def get_parameters_fn( |
| shape: tuple[int, int, int], |
| name: str = ''): |
| parameters = hk.get_parameter( |
| name + '_params', shape, jnp.float32, init=param_init |
| ) |
| def parameters_fn(state, memory, diagnostics, randomness, forcing): |
| del state, memory, diagnostics, randomness, forcing |
| outputs = parameters * jnp.ones(surface_nodal_shape) |
| return pytree_utils.unpack_to_pytree( |
| outputs, output_shapes, self.feature_axis, |
| ) |
| return parameters_fn |
|
|
| if land_embedding is not None: |
| self.land_embedding_fn = land_embedding( |
| coords=coords, |
| dt=dt, |
| physics_specs=physics_specs, |
| aux_features=aux_features, |
| output_shapes=output_shapes, |
| ) |
| else: |
| self.land_embedding_fn = get_parameters_fn(uniform_param_shape, 'land') |
|
|
| if sea_embedding is not None: |
| self.sea_embedding_fn = sea_embedding( |
| coords=coords, |
| dt=dt, |
| physics_specs=physics_specs, |
| aux_features=aux_features, |
| output_shapes=output_shapes, |
| ) |
| else: |
| self.sea_embedding_fn = get_parameters_fn(uniform_param_shape, 'sea') |
|
|
| if sea_ice_embedding is not None: |
| self.sea_ice_embedding_fn = sea_ice_embedding( |
| coords=coords, |
| dt=dt, |
| physics_specs=physics_specs, |
| aux_features=aux_features, |
| output_shapes=output_shapes, |
| ) |
| else: |
| self.sea_ice_embedding_fn = get_parameters_fn( |
| uniform_param_shape, 'sea_ice' |
| ) |
|
|
| self.output_transform_fn = output_transform_module( |
| coords, dt, physics_specs, aux_features |
| ) |
| ds = xarray_utils.ds_from_path_or_aux(static_vars_ds_path, aux_features) |
| self.land_sea_mask = xarray_utils.nodal_land_sea_mask_from_ds(ds) |
|
|
| def __call__( |
| self, |
| state: typing.Pytree, |
| memory: Optional[typing.Pytree] = None, |
| diagnostics: Optional[typing.Pytree] = None, |
| randomness: Optional[typing.Pytree] = None, |
| forcing: Optional[typing.Forcing] = None, |
| ) -> typing.Pytree: |
| """Returns the embedding output on nodal locations.""" |
| |
| land_outputs = self.land_embedding_fn( |
| state, memory, diagnostics, randomness, forcing) |
| sea_outputs = self.sea_embedding_fn( |
| state, memory, diagnostics, randomness, forcing) |
| sea_ice_outputs = self.sea_ice_embedding_fn( |
| state, memory, diagnostics, randomness, forcing |
| ) |
|
|
| |
| land_fraction = self.land_sea_mask |
| sea_fraction = 1 - land_fraction |
| sea_ice_fraction = forcing[xarray_utils.SEA_ICE_COVER] |
|
|
| |
| land_weight = land_fraction |
| sea_ice_weight = sea_ice_fraction * sea_fraction |
| sea_weight = (1 - sea_ice_fraction) * sea_fraction |
|
|
| def tree_scale(a, x): |
| |
| return jax.tree_util.tree_map(lambda y: a * y, x) |
|
|
| surface_outputs = jax.tree_util.tree_map( |
| lambda a, b, c: a + b + c, |
| tree_scale(land_weight, land_outputs), |
| tree_scale(sea_weight, sea_outputs), |
| tree_scale(sea_ice_weight, sea_ice_outputs), |
| ) |
|
|
| return self.output_transform_fn(surface_outputs) |
|
|