Download model/legacy/steps.py from OneScience-Group/NeuralGCM: direct link, hf CLI and curl.
- Browser
- Download file 11.7 kB
-
https://huggingface.co/OneScience-Group/NeuralGCM/resolve/main/model/legacy/steps.py
- Command line
-
hf download hf://OneScience-Group/NeuralGCM/model/legacy/steps.py
-
curl -L -o steps.py https://huggingface.co/OneScience-Group/NeuralGCM/resolve/main/model/legacy/steps.py
11.7 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. | |
| """Modules that parameterize composed time-steppers.""" | |
| import abc | |
| import functools | |
| from typing import Any, Callable, Optional, Sequence | |
| from dinosaur import coordinate_systems | |
| from dinosaur import primitive_equations | |
| from dinosaur import time_integration | |
| from dinosaur import typing | |
| import gin | |
| import haiku as hk | |
| from model.legacy import diagnostics | |
| from model.legacy import integrators | |
| from model.legacy import perturbations | |
| from model.legacy import stochastic | |
| DiagnosticModule = diagnostics.DiagnosticModule | |
| Forcing = typing.Forcing | |
| Pytree = typing.Pytree | |
| ModelState = typing.ModelState | |
| EquationModule = Callable[..., time_integration.ImplicitExplicitODE] | |
| CorrectorModule = typing.CorrectorModule | |
| PerturbationModule = perturbations.PerturbationModule | |
| RandomnessModule = stochastic.RandomnessModule | |
| PyTreeStepFilterModule = typing.PyTreeStepFilterModule | |
| TimeIntegrator = integrators.TimeIntegrator | |
| TransformModule = typing.TransformModule | |
| class BaseStep(abc.ABC): | |
| """Base class for Step modules.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: typing.AuxFeatures, | |
| diagnostics_module: DiagnosticModule = diagnostics.NoDiagnostics, | |
| randomness_module: RandomnessModule = stochastic.NoRandomField, | |
| ): | |
| self.diagnostics_fn = diagnostics_module( | |
| coords, dt, physics_specs, aux_features) | |
| self.randomness_fn = randomness_module( | |
| coords, dt, physics_specs, aux_features) | |
| def __call__( | |
| self, | |
| state: ModelState, | |
| forcing: typing.Forcing, | |
| ) -> ModelState: | |
| """Computes the state of the system evolved in time by `self.dt`.""" | |
| def finalize_state( | |
| self, | |
| x: ModelState, | |
| forcing: typing.Forcing, | |
| ) -> ModelState: | |
| """Finalizes initialization of a model state `x`, encoded from data. | |
| This method ensures that state has all of the `ModelState` fields | |
| initialized in a way compatible with this step function. This includes | |
| populating initial `diagnostics`, `memory` and `randomness` fields. | |
| Args: | |
| x: Initial values for the model state typically provided by the encoder. | |
| forcing: Data covariates from the same time slice as `x`. | |
| Returns: | |
| Initialized model state. | |
| """ | |
| x.randomness = self.randomness_fn.unconditional_sample( | |
| hk.maybe_next_rng_key() | |
| ) | |
| x.diagnostics = self.diagnostics_fn( | |
| x, physics_tendencies=None, forcing=forcing) | |
| return x | |
| class EquationStep(BaseStep, hk.Module): | |
| """Step module that advances the state by integrating an equation in time.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: typing.AuxFeatures, | |
| equation_module: EquationModule, | |
| time_integrator: TimeIntegrator = integrators.imex_rk_sil3, | |
| filter_modules: Sequence[PyTreeStepFilterModule] = tuple(), | |
| checkpoint_explicit_terms: bool = True, | |
| name: Optional[str] = None, | |
| ): | |
| hk.Module.__init__(self, name=name) | |
| BaseStep.__init__(self, coords, dt, physics_specs, aux_features) | |
| equation = equation_module(coords, dt, physics_specs, aux_features) | |
| if checkpoint_explicit_terms: | |
| equation = time_integration.ImplicitExplicitODE.from_functions( | |
| hk.remat(equation.explicit_terms), | |
| equation.implicit_terms, | |
| equation.implicit_inverse) # pyrefly: ignore[bad-argument-type] | |
| step_fn = time_integrator(equation, dt) | |
| filter_fns = [ | |
| module(coords, dt, physics_specs, aux_features) | |
| for module in filter_modules] | |
| self.dt = dt | |
| self.step_fn = time_integration.step_with_filters(step_fn, filter_fns) | |
| def __call__( | |
| self, | |
| x: ModelState, | |
| forcing: Optional[typing.Forcing] = None, | |
| ) -> ModelState: | |
| """Computes the state of the system evolved in time by `dt`.""" | |
| del forcing | |
| next_state = time_integration.maybe_fix_sim_time_roundoff( | |
| self.step_fn(x.state), self.dt) | |
| return ModelState(next_state) # pyrefly: ignore[bad-argument-count] | |
| class RepeatedStep(BaseStep, hk.Module): | |
| """Step module that consists of repeated substeps of the same form.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: typing.AuxFeatures, | |
| inner_step_module: typing.StepModule, | |
| num_inner_steps: int = 1, | |
| name: Optional[str] = None, | |
| ): | |
| hk.Module.__init__(self, name=name) | |
| BaseStep.__init__(self, coords, dt, physics_specs, aux_features) | |
| inner_dt = dt / num_inner_steps | |
| self.step_fn = inner_step_module( | |
| coords, inner_dt, physics_specs, aux_features) | |
| self.num_inner_steps = num_inner_steps | |
| def __call__( | |
| self, | |
| state: ModelState, | |
| forcing: typing.Forcing, | |
| ) -> ModelState: | |
| """Computes the state of the system evolved in time by `dt`.""" | |
| step_fn = functools.partial(self.step_fn, forcing=forcing) | |
| step_fn = time_integration.repeated(step_fn, self.num_inner_steps, hk.scan) | |
| return step_fn(state) | |
| class CustomCoordsStep(BaseStep, hk.Module): | |
| """Step module that uses gin-configured coordinates instead of coords. | |
| This class currently supports model states in spectral representation. It | |
| could be easily extended to nodal-state models by converting to modal space | |
| prior to spectral interpolation and back after the timestep if performed. | |
| """ | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: typing.AuxFeatures, | |
| step_module: typing.StepModule, | |
| custom_coords: coordinate_systems.CoordinateSystem = gin.REQUIRED, # pyrefly: ignore[bad-function-definition] | |
| name: Optional[str] = None, | |
| ): | |
| hk.Module.__init__(self, name=name) | |
| BaseStep.__init__(self, coords, dt, physics_specs, aux_features) | |
| self.step_fn = step_module( | |
| custom_coords, dt, physics_specs, aux_features) | |
| self.to_custom_coords_fn = coordinate_systems.get_spectral_interpolate_fn( | |
| coords, custom_coords) | |
| self.from_custom_coords_fn = coordinate_systems.get_spectral_interpolate_fn( | |
| custom_coords, coords) | |
| def __call__( | |
| self, | |
| x: typing.PyTreeState, | |
| forcing: typing.Forcing, | |
| ) -> typing.PyTreeState: | |
| del forcing # currently not supported. | |
| x = self.to_custom_coords_fn(x) | |
| custom_out = self.step_fn(x, None) | |
| return self.from_custom_coords_fn(custom_out) | |
| class StochasticPhysicsParameterizationStep(BaseStep, hk.Module): | |
| """Step module that uses stochastic physics tendencies with dycore.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: typing.AuxFeatures, | |
| corrector_module: CorrectorModule, | |
| physics_parameterization_module: typing.ParameterizationModule, | |
| num_substeps: int = 1, | |
| diagnostics_module: DiagnosticModule = diagnostics.NoDiagnostics, | |
| randomness_module: RandomnessModule = stochastic.ZerosRandomField, | |
| perturbation_module: PerturbationModule = perturbations.NoPerturbation, | |
| checkpoint_substep: bool = False, | |
| name: Optional[str] = None, | |
| ): | |
| hk.Module.__init__(self, name=name) | |
| BaseStep.__init__( | |
| self, coords, dt, physics_specs, aux_features, | |
| diagnostics_module=diagnostics_module, | |
| randomness_module=randomness_module) | |
| inner_dt = dt / num_substeps | |
| self.num_substeps = num_substeps | |
| self.corrector_fn = corrector_module( | |
| coords, inner_dt, physics_specs, aux_features) | |
| self.physics_parameterization_fn = physics_parameterization_module( | |
| coords, inner_dt, physics_specs, aux_features) | |
| self.perturbation_fn = perturbation_module( | |
| coords, inner_dt, physics_specs, aux_features) | |
| self.checkpoint_substep = checkpoint_substep | |
| self.coords = coords | |
| def finalize_state( | |
| self, | |
| x: ModelState, | |
| forcing: typing.Forcing, | |
| ) -> ModelState: | |
| """Finalizes initialization of a model state `x`, encoded from data. | |
| This method ensures that state has all of the `ModelState` fields | |
| initialized in a way compatible with this step function. This includes | |
| populating initial `diagnostics`, `memory` and `randomness` fields. | |
| This is called by StochasticModularStepModel.encode, after encoding the data | |
| Args: | |
| x: Initial values for the model state typically provided by the encoder. | |
| forcing: Data covariates from the same time slice as `x`. | |
| Returns: | |
| Initialized model state. | |
| """ | |
| # TODO(dkochkov) Consider adding an option of not overriding randomness. | |
| x.randomness = self.randomness_fn.unconditional_sample( | |
| hk.maybe_next_rng_key() | |
| ) | |
| pp_tendency = self.physics_parameterization_fn( | |
| x.state, x.memory, x.diagnostics, x.randomness.nodal_value, forcing | |
| ) | |
| x.diagnostics = self.diagnostics_fn(x, pp_tendency, forcing) | |
| return x | |
| def __call__( | |
| self, | |
| state: ModelState, | |
| forcing: typing.Forcing, | |
| ) -> ModelState: | |
| """Computes the state of the system evolved in time by `dt`.""" | |
| def step_fn(x): | |
| x = self.coords.with_dycore_sharding(x) | |
| # TODO(dkochkov) Consider passing `x` to physics_parameterization. | |
| pp_tendency = self.physics_parameterization_fn( | |
| x.state, x.memory, x.diagnostics, x.randomness.nodal_value, forcing | |
| ) | |
| pp_tendency = self.perturbation_fn( | |
| pp_tendency, | |
| state=x.state, | |
| randomness=x.randomness.nodal_value, | |
| ) | |
| next_state = self.corrector_fn(x.state, pp_tendency, forcing) | |
| # TODO(dkochkov) update stochastic modules to take optional state. | |
| next_randomness = self.randomness_fn.advance(x.randomness) | |
| next_memory = x.state if x.memory is not None else None | |
| next_diagnostics = self.diagnostics_fn(x, pp_tendency, forcing) | |
| x_next = ModelState( | |
| state=next_state, memory=next_memory, diagnostics=next_diagnostics, # pyrefly: ignore[unexpected-keyword] | |
| randomness=next_randomness) # pyrefly: ignore[unexpected-keyword] | |
| x_next = self.coords.with_dycore_sharding(x_next) | |
| return x_next | |
| if self.checkpoint_substep: | |
| step_fn = hk.remat(step_fn) | |
| step_fn = time_integration.repeated(step_fn, self.num_substeps, hk.scan) | |
| return step_fn(state) | |
| # TODO(dkochkov) Move vertical advection step to transforms.py. | |
| class SemiLagrangianVerticalAdvectionStep(hk.Module): | |
| """Step module that applies vertical advection for the primitive equations.""" | |
| def __init__( | |
| self, | |
| coords: coordinate_systems.CoordinateSystem, | |
| dt: float, | |
| physics_specs: Any, | |
| aux_features: typing.AuxFeatures, | |
| name: Optional[str] = None, | |
| ): | |
| super().__init__(name=name) | |
| self.coords = coords | |
| self.dt = dt | |
| def __call__(self, state): | |
| return primitive_equations.semi_lagrangian_vertical_advection_step( | |
| state, self.coords, self.dt | |
| ) | |