| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """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) |
|
|
| @abc.abstractmethod |
| 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 |
|
|
|
|
| @gin.register |
| 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) |
| 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) |
|
|
|
|
| @gin.register |
| 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) |
|
|
|
|
| @gin.register |
| 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, |
| 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 |
| x = self.to_custom_coords_fn(x) |
| custom_out = self.step_fn(x, None) |
| return self.from_custom_coords_fn(custom_out) |
|
|
|
|
| @gin.register |
| 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. |
| """ |
| |
| 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) |
| |
| 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) |
| |
| 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, |
| randomness=next_randomness) |
| 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) |
|
|
|
|
| |
|
|
|
|
| @gin.register |
| 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 |
| ) |
|
|