Stommel thermohaline boxes
Stommel's two-box thermohaline circulation, bistable between a thermal and a haline mode.
- Name:
stommel - Field:
physics/oceanography - Formalism: ordinary differential equations
- References: Stommel 1961; Cessi 1994; Ghil and Lucarini 2020
Factory
@register_model(
field='physics/oceanography',
tags=('ode', 'bistability', 'thermohaline circulation', 'tipping'),
references=('Stommel 1961', 'Cessi 1994', 'Ghil and Lucarini 2020'),
defaults={'until': 50.0, 'dt': 0.1},
)
def stommel(
T: float = 3.0,
S: float = 0.0,
*,
eta1: float = 3.0,
eta2: float = 1.2,
eta3: float = 0.3,
) -> System:
"""Return Stommel's two-box thermohaline circulation, bistable between a thermal and a haline mode.
The dimensionless differences of temperature `T` and salinity `S` between an equatorial and a polar box relax to
their forcing and are mixed by a flow proportional to the density difference `|T - S|`:
`dT/dt = eta1 - T (1 + |T - S|)` and `dS/dt = eta2 - S (eta3 + |T - S|)`. For the default parameters a strong
circulation driven by temperature and a weak one driven by salinity are both stable.
Args:
T:
The initial temperature difference.
S:
The initial salinity difference.
eta1:
The thermal forcing.
eta2:
The freshwater forcing.
eta3:
The ratio of the relaxation times of temperature and salinity.
Returns:
system:
The model, with the differences `ocean.T` and `ocean.S`.
Examples:
>>> from skcomplex.models.physics.oceanography import stommel
>>> stommel(eta2=1.0).elements[0].eta2
1.0
"""
return System(elements=[StommelBoxesElement('ocean', T=T, S=S, eta1=eta1, eta2=eta2, eta3=eta3)])
Components
class StommelBoxesElement(Element):
"""Ocean of Stommel's two boxes, the differences of temperature and salinity between the equator and the pole.
Both differences relax to their forcing and are mixed by a flow proportional to the density difference
`|T - S|`: `dT/dt = eta1 - T (1 + |T - S|)` and `dS/dt = eta2 - S (eta3 + |T - S|)`.
Args:
label:
The label of the ocean.
visual:
How the element is drawn, or `None` for `default_visual_`.
T:
The initial temperature difference.
S:
The initial salinity difference.
eta1:
The thermal forcing.
eta2:
The freshwater forcing.
eta3:
The ratio of the relaxation times of temperature and salinity.
Examples:
>>> from skcomplex.models.physics.oceanography import StommelBoxesElement
>>> StommelBoxesElement('ocean').eta2
1.2
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
T: Annotated[float, StateSpec()] = 3.0,
S: Annotated[float, StateSpec()] = 0.0,
eta1: Annotated[float, ParameterSpec()] = 3.0,
eta2: Annotated[float, ParameterSpec()] = 1.2,
eta3: Annotated[float, ParameterSpec()] = 0.3,
) -> None:
super().__init__(label, visual=visual)
self.T = T
self.S = S
self.eta1 = eta1
self.eta2 = eta2
self.eta3 = eta3
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the relaxation of both differences to their forcing and their mixing by the circulation.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The rate laws of the temperature and salinity differences.
"""
return [
RateLaw(s.T, s.eta1 - s.T * (1 + abs_symbolic(s.T - s.S))),
RateLaw(s.S, s.eta2 - s.S * (s.eta3 + abs_symbolic(s.T - s.S))),
]
Equations
ocean.T: diff(T_ocean(t), t) == -(abs(-S_ocean(t) + T_ocean(t)) + 1)*T_ocean(t) + eta1_ocean
ocean.S: diff(S_ocean(t), t) == -(eta3_ocean + abs(-S_ocean(t) + T_ocean(t)))*S_ocean(t) + eta2_ocean