Download model/legacy/decoders.py from OneScience-Group/NeuralGCM: direct link, hf CLI and curl.
- Browser
- Download file 26.9 kB
-
https://huggingface.co/OneScience-Group/NeuralGCM/resolve/main/model/legacy/decoders.py
- Command line
-
hf download hf://OneScience-Group/NeuralGCM/model/legacy/decoders.py
-
curl -L -o decoders.py https://huggingface.co/OneScience-Group/NeuralGCM/resolve/main/model/legacy/decoders.py
26.9 kB
| # Copyright 2024 Google LLC | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # https://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """Defines `decoder` modules that map model state to output data format.""" | |
| import functools | |
| from typing import Any, Callable, Dict, Optional, Tuple, TypeVar | |
| import zlib | |
| from dinosaur import coordinate_systems | |
| from dinosaur import primitive_equations | |
| from dinosaur import pytree_utils | |
| from dinosaur import scales | |
| from dinosaur import spherical_harmonic | |
| from dinosaur import typing | |
| from dinosaur import vertical_interpolation | |
| from dinosaur import weatherbench_utils | |
| from dinosaur import xarray_utils | |
| import gin | |
| import haiku as hk | |
| import jax | |
| import jax.numpy as jnp | |
| from model.legacy import diagnostics | |
| from model.legacy import features | |
| from model.legacy import filters | |
| from model.legacy import mappings | |
| from model.legacy import orographies | |
| from model.legacy import perturbations | |
| from model.legacy import stochastic | |
| from model.legacy import transforms | |
| import numpy as np | |
| # long lines are better than splitting argument definitions onto two lines | |
| # pylint: disable=line-too-long | |
| # We ♥ λ's | |
| # pylint: disable=g-long-lambda | |
| DataState = typing.DataState | |
| DiagnosticModule = diagnostics.DiagnosticModule | |
| FeaturesModule = features.FeaturesModule | |
| FilterModule = Callable[..., typing.PyTreeFilterFn] | |
| Forcing = typing.Forcing | |
| MappingModule = mappings.MappingModule | |
| PyTreeState = typing.PyTreeState | |
| ModelState = typing.ModelState | |
| TransformModule = typing.TransformModule | |
| OrographyModule = orographies.OrographyModule | |
| PerturbationModule = perturbations.PerturbationModule | |
| RandomnessModule = stochastic.RandomnessModule | |
| class DecoderIdentityTransform(hk.Module): | |
| """Transformation that returns inputs without modification.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| name: Optional[str] = None, | |
| ): | |
| super().__init__(name=name) | |
| del coords, dt, physics_specs, aux_features, output_coords | |
| def __call__(self, inputs: PyTreeState) -> PyTreeState: | |
| return inputs | |
| class DecoderFilterTransform(hk.Module): | |
| """Transformation that returns truncated and filtered modal inputs.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| filter_module: FilterModule = filters.DataNoFilter, | |
| return_nodal: bool = True, | |
| name: Optional[str] = None, | |
| ): | |
| super().__init__(name=name) | |
| self.output_coords = output_coords | |
| self.filter_fn = filter_module(coords, dt, physics_specs, aux_features) | |
| self.return_nodal = return_nodal | |
| def __call__(self, inputs: PyTreeState) -> PyTreeState: | |
| modal_inputs = coordinate_systems.maybe_to_modal(inputs, self.output_coords) | |
| filtered_inputs = self.filter_fn(modal_inputs) | |
| if self.return_nodal: | |
| return self.output_coords.horizontal.to_nodal(filtered_inputs) | |
| return filtered_inputs | |
| class OutputModalToModalTransform(hk.Module): | |
| """Transformation that truncates modal state to output coords.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| name: Optional[str] = None, | |
| ): | |
| super().__init__(name=name) | |
| self.coords = coords | |
| self.output_coords = output_coords | |
| def __call__(self, inputs: PyTreeState) -> PyTreeState: | |
| downsample_fn = coordinate_systems.get_spectral_downsample_fn( | |
| self.coords, self.output_coords | |
| ) | |
| return downsample_fn(inputs) | |
| class OutputModalToNodalTransform(hk.Module): | |
| """Transformation that converts modal state to nodal representation.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| name: Optional[str] = None, | |
| ): | |
| super().__init__(name=name) | |
| self.coords = coords | |
| self.output_coords = output_coords | |
| def __call__(self, inputs: PyTreeState) -> PyTreeState: | |
| to_nodal_fn = self.output_coords.horizontal.to_nodal | |
| downsample_fn = coordinate_systems.get_spectral_downsample_fn( | |
| self.coords, self.output_coords | |
| ) | |
| return jax.tree_util.tree_map( | |
| lambda x: to_nodal_fn(downsample_fn(x)), inputs | |
| ) | |
| class OutputNodalToModalTransform(hk.Module): | |
| """Transformation that converts nodal state to modal representation.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| name: Optional[str] = None, | |
| ): | |
| super().__init__(name=name) | |
| self.output_coords = output_coords | |
| def __call__(self, inputs: PyTreeState) -> PyTreeState: | |
| return self.output_coords.horizontal.to_modal(inputs) | |
| class ModalOutputLearnedAdaptorTransform(hk.Module): | |
| """Transformation using a tower to adapt modal outputs to the data domain.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| modal_to_nodal_features_module: FeaturesModule, | |
| nodal_mapping_module: MappingModule, | |
| output_transform_module: TransformModule, | |
| name: Optional[str] = None, | |
| ): | |
| del output_coords # unused. | |
| super().__init__(name=name) | |
| self.coords = coords | |
| 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 | |
| ) | |
| self.get_nodal_shape_fn = lambda x: coordinate_systems.get_nodal_shapes( | |
| x, coords | |
| ) | |
| def __call__(self, inputs: PyTreeState) -> PyTreeState: | |
| """Applies transform to modal inputs, returns modal outputs.""" | |
| inputs, from_dict_fn = pytree_utils.as_dict(inputs) | |
| prediction_shapes = jax.tree_util.tree_map(self.get_nodal_shape_fn, inputs) | |
| # if `inputs` contain `sim_time` - remove it from corrections. | |
| sim_time_shape = prediction_shapes.pop('sim_time', None) | |
| net = self.nodal_mapping_module(prediction_shapes) | |
| nodal_input_features = self.modal_to_nodal_features_fn(inputs, None) | |
| nodal_corrections = self.output_transform_fn(net(nodal_input_features)) | |
| corrections = self.coords.horizontal.to_modal(nodal_corrections) | |
| if sim_time_shape is not None: | |
| corrections['sim_time'] = 0.0 | |
| outputs = jax.tree_util.tree_map(lambda x, y: x + y, inputs, corrections) | |
| return from_dict_fn(outputs) | |
| class NodalOutputLearnedAdaptorTransform(hk.Module): | |
| """Transformation using a tower to adapt nodal outputs to the data domain.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| nodal_to_nodal_features_module: FeaturesModule, | |
| nodal_mapping_module: MappingModule, | |
| output_transform_module: TransformModule, | |
| name: Optional[str] = None, | |
| ): | |
| del output_coords # unused. | |
| super().__init__(name=name) | |
| self.coords = coords | |
| self.nodal_to_nodal_features_fn = nodal_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 | |
| ) | |
| self.get_nodal_shape_fn = lambda x: coordinate_systems.get_nodal_shapes( | |
| x, coords | |
| ) | |
| def __call__(self, inputs: PyTreeState) -> PyTreeState: | |
| """Applies transform to nodal inputs, returns nodal outputs.""" | |
| inputs, from_dict_fn = pytree_utils.as_dict(inputs) | |
| prediction_shapes = jax.tree_util.tree_map(self.get_nodal_shape_fn, inputs) | |
| # if `inputs` contain `sim_time` - remove it from corrections. | |
| sim_time_shape = prediction_shapes.pop('sim_time', None) | |
| net = self.nodal_mapping_module(prediction_shapes) | |
| input_features = self.nodal_to_nodal_features_fn(inputs, None) | |
| corrections = self.output_transform_fn(net(input_features)) | |
| if sim_time_shape is not None: | |
| corrections['sim_time'] = 0.0 | |
| outputs = jax.tree_util.tree_map(lambda x, y: x + y, inputs, corrections) | |
| return from_dict_fn(outputs) | |
| class DecoderCombinedTransform(hk.Module): | |
| """Module that applies multiple transformations sequentially.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: typing.AuxFeatures, | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| transforms: Tuple[TransformModule, ...], # pylint: disable=redefined-outer-name | |
| name: Optional[str] = None, | |
| ): | |
| super().__init__(name=name) | |
| self.transform_fns = [ | |
| module(coords, dt, physics_specs, aux_features, output_coords) | |
| for module in transforms | |
| ] | |
| def __call__(self, inputs: PyTreeState) -> PyTreeState: | |
| for transform_fn in self.transform_fns: | |
| inputs = transform_fn(inputs) | |
| return inputs | |
| class IdentityDecoder(hk.Module): | |
| """Decoder that returns model state unaltered.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| name: Optional[str] = None, | |
| ): | |
| del coords, dt, physics_specs, aux_features, output_coords | |
| super().__init__(name=name) | |
| def __call__(self, x: ModelState, forcing: Forcing) -> DataState: | |
| del forcing | |
| return x.state | |
| class StateToDictDecoder(hk.Module): | |
| """Decoder that returns a dict representation of a model state.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| transform_module: TransformModule = DecoderIdentityTransform, | |
| name: Optional[str] = None, | |
| ): | |
| super().__init__(name=name) | |
| self.transform_fn = transform_module( | |
| coords, dt, physics_specs, aux_features, output_coords | |
| ) | |
| def __call__(self, x: ModelState, forcing: Forcing) -> DataState: | |
| del forcing | |
| state_dict, _ = pytree_utils.as_dict(x.state) | |
| return self.transform_fn(state_dict) | |
| class LeapfrogSliceDecoder(hk.Module): | |
| """Decoder that returns one slice out of a leapfrog pair.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| slice_id: int = 0, | |
| transform_module: TransformModule = DecoderIdentityTransform, | |
| name: Optional[str] = None, | |
| ): | |
| super().__init__(name=name) | |
| self.slice_id = slice_id | |
| self.transform_fn = transform_module( | |
| coords, dt, physics_specs, aux_features, output_coords | |
| ) | |
| def __call__(self, x: ModelState, forcing: Forcing) -> DataState: | |
| del forcing | |
| return self.transform_fn(x.state[self.slice_id]) | |
| class LeapfrogSliceDictDecoder(hk.Module): | |
| """Decoder that returns one slice out of a leapfrog pair as dictionary.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| slice_id: int = 0, | |
| transform_module: TransformModule = DecoderIdentityTransform, | |
| name: Optional[str] = None, | |
| ): | |
| super().__init__(name=name) | |
| self.slice_id = slice_id | |
| self.transform_fn = transform_module( | |
| coords, dt, physics_specs, aux_features, output_coords | |
| ) | |
| def __call__(self, x: ModelState, forcing: Forcing) -> DataState: | |
| del forcing | |
| state_dict, _ = pytree_utils.as_dict(x.state[self.slice_id]) | |
| return self.transform_fn(state_dict) | |
| class PrimitiveToWeatherbenchDecoder(hk.Module): | |
| """Decoder that converts `StateWithTime` to `weatherbench.State`.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| time_axis: int = 0, | |
| orography_module: OrographyModule = orographies.ClippedOrography, | |
| transform_module: TransformModule = DecoderIdentityTransform, | |
| name: Optional[str] = None, | |
| ): | |
| super().__init__(name=name) | |
| ref_temps = aux_features[xarray_utils.REF_TEMP_KEY] | |
| self.ref_temps = ref_temps[..., np.newaxis, np.newaxis] | |
| self.output_coords = output_coords | |
| self.coords = coords | |
| self.physics_specs = physics_specs | |
| self.velocity_fn = functools.partial( | |
| spherical_harmonic.vor_div_to_uv_nodal, | |
| output_coords.horizontal, | |
| ) | |
| modal_orography_init_fn = orography_module( | |
| coords, dt, physics_specs, aux_features | |
| ) | |
| orography = modal_orography_init_fn() # pytype: disable=not-callable # jax-ndarray | |
| self.nodal_orography = coords.horizontal.to_nodal(orography) | |
| self.geopotential_fn = functools.partial( | |
| primitive_equations.get_geopotential_with_moisture, | |
| nodal_orography=self.nodal_orography, | |
| coordinates=coords.vertical, | |
| gravity_acceleration=physics_specs.gravity_acceleration, | |
| ideal_gas_constant=physics_specs.ideal_gas_constant, | |
| water_vapor_gas_constant=physics_specs.water_vapor_gas_constant, | |
| ) | |
| self.transform_fn = transform_module( | |
| coords, dt, physics_specs, aux_features, output_coords | |
| ) | |
| def primitive_to_weatherbench( | |
| self, | |
| inputs: primitive_equations.StateWithTime, | |
| ) -> weatherbench_utils.State: | |
| """Converts pe_state to weatherbench state on pressure levels.""" | |
| # output state is computed on output_coords. | |
| to_nodal_fn = self.output_coords.horizontal.to_nodal | |
| u, v = self.velocity_fn( # returned in nodal space. | |
| vorticity=inputs.vorticity, divergence=inputs.divergence | |
| ) | |
| t = self.ref_temps + to_nodal_fn(inputs.temperature_variation) | |
| tracers = to_nodal_fn(inputs.tracers) | |
| z = self.geopotential_fn(t, tracers['specific_humidity']) | |
| surface_pressure = jnp.exp(to_nodal_fn(inputs.log_surface_pressure)) | |
| u, v, t, z, tracers, surface_pressure = ( | |
| self.coords.dycore_to_physics_sharding( | |
| (u, v, t, z, tracers, surface_pressure) | |
| ) | |
| ) | |
| interpolate_with_linear_extrap_fn = ( | |
| vertical_interpolation.vectorize_vertical_interpolation( | |
| vertical_interpolation.linear_interp_with_linear_extrap | |
| ) | |
| ) | |
| interpolate_with_constant_extrap_fn = ( | |
| vertical_interpolation.vectorize_vertical_interpolation( | |
| vertical_interpolation.vertical_interpolation | |
| ) | |
| ) | |
| regrid_with_linear_fn = functools.partial( | |
| vertical_interpolation.interp_sigma_to_pressure, | |
| pressure_coords=self.output_coords.vertical, | |
| sigma_coords=self.coords.vertical, | |
| surface_pressure=surface_pressure, | |
| interpolate_fn=interpolate_with_linear_extrap_fn, | |
| ) | |
| regrid_with_constant_fn = functools.partial( | |
| vertical_interpolation.interp_sigma_to_pressure, | |
| pressure_coords=self.output_coords.vertical, | |
| sigma_coords=self.coords.vertical, | |
| surface_pressure=surface_pressure, | |
| interpolate_fn=interpolate_with_constant_extrap_fn, | |
| ) | |
| # closes regridding options based on http://shortn/_X09ZAU1jsx. | |
| # use constant extrapolation for `u, v, tracers`. | |
| # use linear extrapolation for `z, t`. | |
| return weatherbench_utils.State( | |
| u=regrid_with_constant_fn(u), # pyrefly: ignore[unexpected-keyword] | |
| v=regrid_with_constant_fn(v), # pyrefly: ignore[unexpected-keyword] | |
| t=regrid_with_linear_fn(t), # pyrefly: ignore[unexpected-keyword] | |
| z=regrid_with_linear_fn(z), # pyrefly: ignore[unexpected-keyword] | |
| sim_time=inputs.sim_time, # pyrefly: ignore[unexpected-keyword] | |
| tracers=regrid_with_constant_fn(tracers), # pyrefly: ignore[unexpected-keyword] | |
| ) | |
| def __call__( | |
| self, inputs: ModelState, forcing: Forcing | |
| ) -> DataState: | |
| del forcing | |
| wb_on_sigma = self.primitive_to_weatherbench(inputs.state) | |
| return self.transform_fn(wb_on_sigma.asdict()) # pyrefly: ignore[missing-attribute] | |
| _DECODER_SALT = zlib.crc32(b'decoder') # arbitrary uint32 value | |
| def _decoder_prng_key( | |
| randomness: typing.RandomnessState, | |
| ) -> typing.PRNGKeyArray | None: | |
| """Get a PRNG Key suitable for decoder randomness.""" | |
| if randomness.prng_key is None: | |
| return None | |
| salt = jnp.uint32(_DECODER_SALT) + jnp.uint32(randomness.prng_step) | |
| return jax.random.fold_in(randomness.prng_key, salt) | |
| class LearnedPrimitiveToWeatherbenchDecoder(PrimitiveToWeatherbenchDecoder): | |
| """Similar to `PrimitiveToWeatherbenchDecoder` with learned interpolation.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| modal_to_nodal_model_features_module: FeaturesModule, | |
| modal_to_nodal_data_features_module: FeaturesModule, | |
| correction_transform_module: TransformModule, | |
| nodal_mapping_module: MappingModule, | |
| prediction_mask: typing.Pytree, | |
| time_axis: int = 0, | |
| orography_module: OrographyModule = orographies.ClippedOrography, | |
| transform_module: TransformModule = DecoderIdentityTransform, | |
| randomness_module: RandomnessModule = stochastic.ZerosRandomField, | |
| perturbation_module: PerturbationModule = perturbations.NoPerturbation, | |
| diagnostics_module: DiagnosticModule = diagnostics.NoDiagnostics, | |
| name: Optional[str] = None, | |
| ): | |
| super().__init__( | |
| coords=coords, | |
| dt=dt, | |
| physics_specs=physics_specs, | |
| aux_features=aux_features, | |
| output_coords=output_coords, | |
| time_axis=time_axis, | |
| orography_module=orography_module, | |
| name=name, | |
| ) # don't pass the transform, as we apply it at the end. | |
| self.prediction_mask = prediction_mask | |
| # features are computed on both coordinate systems. | |
| self.model_features_fn = modal_to_nodal_model_features_module( | |
| coords, dt, physics_specs, aux_features | |
| ) | |
| self.data_features_fn = modal_to_nodal_data_features_module( | |
| output_coords, dt, physics_specs, aux_features | |
| ) | |
| self.corrections_transform_fn = correction_transform_module( | |
| coords, dt, physics_specs, aux_features | |
| ) | |
| # corrections are computed in real space on output coordinates. | |
| self.nodal_mapping_module = nodal_mapping_module | |
| self.get_nodal_shape_fn = lambda x: coordinate_systems.get_nodal_shapes( | |
| x, output_coords | |
| ) | |
| self.transform_fn = transform_module( | |
| coords, dt, physics_specs, aux_features, output_coords | |
| ) | |
| self.randomness_fn = randomness_module( | |
| coords, dt, physics_specs, aux_features | |
| ) | |
| self.perturbation_fn = perturbation_module( | |
| coords, dt, physics_specs, aux_features | |
| ) | |
| self.diagnostic_fn = diagnostics_module( | |
| coords, dt, physics_specs, aux_features | |
| ) | |
| def __call__( | |
| self, inputs: ModelState, forcing: Forcing | |
| ) -> DataState: | |
| randomness = self.randomness_fn.unconditional_sample( | |
| _decoder_prng_key(inputs.randomness) | |
| ) | |
| prognostics = self.perturbation_fn( | |
| inputs=self.coords.with_dycore_sharding(inputs.state), | |
| state=None, | |
| randomness=self.coords.with_dycore_sharding(randomness.nodal_value), | |
| ) | |
| inputs.state = prognostics # compute diagnostics from the perturbed state. | |
| # TODO(dkochkov) Could we pass physics_tendencies here? | |
| # TODO(janniyuval) Consider using evaporation diagnostics for training. | |
| decoder_diagnostics = self.diagnostic_fn(inputs, None) | |
| wb_on_pressure_dict = self.primitive_to_weatherbench(prognostics).asdict() # pyrefly: ignore[missing-attribute] | |
| wb_on_pressure_modal = coordinate_systems.maybe_to_modal( | |
| self.coords.physics_to_dycore_sharding(wb_on_pressure_dict), | |
| self.output_coords, | |
| ) | |
| wb_on_pressure_dict['diagnostics'] = decoder_diagnostics | |
| prediction_mask = pytree_utils.replace_with_matching_or_default( | |
| wb_on_pressure_dict, self.prediction_mask, default=False) | |
| prediction_shapes = jax.tree_util.tree_map( | |
| lambda x, y: self.get_nodal_shape_fn(x) if y else None, | |
| wb_on_pressure_dict, | |
| prediction_mask, | |
| ) | |
| net = self.nodal_mapping_module(prediction_shapes) | |
| model_features = self.model_features_fn( | |
| prognostics.asdict(), forcing=forcing, | |
| randomness=randomness.nodal_value | |
| ) | |
| data_features = self.data_features_fn(wb_on_pressure_modal, forcing=forcing) | |
| data_features = transforms.add_prefix(data_features, 'data_') | |
| model_features = transforms.add_prefix(model_features, 'model_') | |
| all_features = self.coords.dycore_to_physics_sharding( | |
| data_features | model_features | |
| ) | |
| nodal_outputs = self.corrections_transform_fn(net(all_features)) | |
| add_fn = lambda x, y: x + y if y is not None else x | |
| wb_on_pressure_dict = jax.tree_util.tree_map( | |
| add_fn, wb_on_pressure_dict, nodal_outputs | |
| ) | |
| return self.transform_fn(wb_on_pressure_dict) | |
| class DimensionalPrimitiveToWeatherbenchDecoder(PrimitiveToWeatherbenchDecoder): | |
| """Same as PrimitiveToWeatherbenchDecoder, but with dimensional output.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| inputs_to_units_mapping: Dict[str, str], | |
| time_axis: int = 0, | |
| orography_module: OrographyModule = orographies.ClippedOrography, | |
| transform_module: TransformModule = DecoderIdentityTransform, | |
| name: Optional[str] = None, | |
| ): | |
| nondim_pressure_centers = physics_specs.nondimensionalize( | |
| output_coords.vertical.centers * scales.units.millibar | |
| ) | |
| nondim_output_coords = coordinate_systems.CoordinateSystem( | |
| output_coords.horizontal, | |
| vertical_interpolation.PressureCoordinates(nondim_pressure_centers), | |
| spmd_mesh=output_coords.spmd_mesh, | |
| ) | |
| super().__init__( | |
| coords, | |
| dt, | |
| physics_specs, | |
| aux_features, | |
| output_coords=nondim_output_coords, | |
| time_axis=time_axis, | |
| orography_module=orography_module, | |
| transform_module=transform_module, | |
| name=name, | |
| ) | |
| self.redimensionalize_fn = transforms.RedimensionalizeTransform( | |
| coords, | |
| dt, | |
| physics_specs, | |
| aux_features, | |
| output_coords=output_coords, | |
| inputs_to_units_mapping=inputs_to_units_mapping, | |
| ) | |
| def __call__( | |
| self, inputs: ModelState, forcing: Forcing | |
| ) -> DataState: | |
| return self.redimensionalize_fn(super().__call__(inputs, forcing)) | |
| class DimensionalLearnedPrimitiveToWeatherbenchDecoder( | |
| LearnedPrimitiveToWeatherbenchDecoder | |
| ): | |
| """Same as LearnedPrimitiveToWeatherbenchDecoder, but with dimensional output.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: Dict[str, Any], | |
| output_coords: coordinate_systems.CoordinateSystem, | |
| modal_to_nodal_model_features_module: FeaturesModule, | |
| modal_to_nodal_data_features_module: FeaturesModule, | |
| nodal_mapping_module: MappingModule, | |
| correction_transform_module: TransformModule, | |
| prediction_mask: typing.Pytree, | |
| inputs_to_units_mapping: Dict[str, str], | |
| time_axis: int = 0, | |
| orography_module: OrographyModule = orographies.ClippedOrography, | |
| transform_module: TransformModule = DecoderIdentityTransform, | |
| randomness_module: RandomnessModule = stochastic.ZerosRandomField, | |
| perturbation_module: PerturbationModule = perturbations.NoPerturbation, | |
| diagnostics_module: DiagnosticModule = diagnostics.NoDiagnostics, | |
| name: Optional[str] = None, | |
| ): | |
| nondim_pressure_centers = physics_specs.nondimensionalize( | |
| output_coords.vertical.centers * scales.units.millibar | |
| ) | |
| nondim_output_coords = coordinate_systems.CoordinateSystem( | |
| output_coords.horizontal, | |
| vertical_interpolation.PressureCoordinates(nondim_pressure_centers), | |
| spmd_mesh=output_coords.spmd_mesh, | |
| ) | |
| super().__init__( | |
| coords, | |
| dt, | |
| physics_specs, | |
| aux_features, | |
| output_coords=nondim_output_coords, | |
| modal_to_nodal_model_features_module=( | |
| modal_to_nodal_model_features_module | |
| ), | |
| modal_to_nodal_data_features_module=modal_to_nodal_data_features_module, | |
| nodal_mapping_module=nodal_mapping_module, | |
| correction_transform_module=correction_transform_module, | |
| prediction_mask=prediction_mask, | |
| time_axis=time_axis, | |
| orography_module=orography_module, | |
| transform_module=transform_module, | |
| randomness_module=randomness_module, | |
| perturbation_module=perturbation_module, | |
| diagnostics_module=diagnostics_module, | |
| name=name, | |
| ) | |
| self.redimensionalize_fn = transforms.RedimensionalizeTransform( | |
| coords, | |
| dt, | |
| physics_specs, | |
| aux_features, | |
| output_coords=output_coords, | |
| inputs_to_units_mapping=inputs_to_units_mapping, | |
| ) | |
| def __call__( | |
| self, inputs: ModelState, forcing: Forcing | |
| ) -> DataState: | |
| return self.redimensionalize_fn(super().__call__(inputs, forcing)) | |