Saltzman-Maasch glaciation
The glacial cycles of Saltzman and Maasch, a self-sustained oscillation of ice, carbon, and ocean.
- Name:
saltzman_glaciation - Field:
physics/climate - Formalism: ordinary differential equations
- References: Saltzman and Maasch 1991; Datseris and Parlitz 2022
Factory
@register_model(
field='physics/climate',
tags=('ode', 'limit cycle', 'ice ages', 'paleoclimate'),
references=('Saltzman and Maasch 1991', 'Datseris and Parlitz 2022'),
defaults={'until': 50.0, 'dt': 0.05},
)
def saltzman_glaciation(
x: float = -0.2,
y: float = 1.0,
z: float = 0.0,
*,
p: float = 1.0,
q: float = 2.5,
r: float = 1.3,
s: float = 0.6,
u: float = 0.2,
nu: float = 0.0,
) -> System:
"""Return the glacial cycles of Saltzman and Maasch, a self-sustained oscillation of ice, carbon, and ocean.
The anomalies of global ice volume `x`, atmospheric carbon dioxide `y`, and deep ocean temperature `z` follow
`dx/dt = -x - y - u z - nu F(t)`, `dy/dt = -p z + r y - s y^2 - y^3`, and `dz/dt = -q (x + z)`. The time unit is
10 kyr, and `F(t) = cos(2π t / 2.21) - cos(2π t / 4.41)` is a quasiperiodic orbital forcing with the periods of
precession and obliquity. Without forcing the default parameters give a limit cycle of about 100 kyr.
Args:
x:
The initial ice volume anomaly.
y:
The initial carbon dioxide anomaly.
z:
The initial deep ocean temperature anomaly.
p:
The effect of the deep ocean temperature on carbon dioxide.
q:
The relaxation rate of the deep ocean temperature to the ice volume.
r:
The growth rate of carbon dioxide by its own feedback.
s:
The quadratic saturation of carbon dioxide.
u:
The effect of the deep ocean temperature on ice volume.
nu:
The amplitude of the orbital forcing.
Returns:
system:
The model, with the anomalies `climate.x`, `climate.y`, and `climate.z`.
Examples:
>>> from skcomplex.models.physics.climate import saltzman_glaciation
>>> saltzman_glaciation(nu=0.5).elements[0].nu
0.5
"""
return System(elements=[SaltzmanMaaschElement('climate', x=x, y=y, z=z, p=p, q=q, r=r, s=s, u=u, nu=nu)])
Components
class SaltzmanMaaschElement(Element):
"""Climate of the Saltzman-Maasch glacial cycles, the anomalies of ice, carbon dioxide, and deep ocean temperature.
The anomalies of global ice volume `x`, atmospheric carbon dioxide `y`, and deep ocean temperature `z` follow
`dx/dt = -x - y - u z - nu F(t)`, `dy/dt = -p z + r y - s y^2 - y^3`, and `dz/dt = -q (x + z)`, where
`F(t) = cos(2π t0 t / 22.1) - cos(2π t0 t / 44.1)` is the orbital forcing of precession and obliquity.
Args:
label:
The label of the climate.
visual:
How the element is drawn, or `None` for `default_visual_`.
x:
The initial ice volume anomaly.
y:
The initial carbon dioxide anomaly.
z:
The initial deep ocean temperature anomaly.
p:
The effect of the deep ocean temperature on carbon dioxide.
q:
The relaxation rate of the deep ocean temperature to the ice volume.
r:
The growth rate of carbon dioxide by its own feedback.
s:
The quadratic saturation of carbon dioxide.
u:
The effect of the deep ocean temperature on ice volume.
nu:
The amplitude of the orbital forcing.
t0:
The time unit in thousands of years, which scales the orbital periods.
Examples:
>>> from skcomplex.models.physics.climate import SaltzmanMaaschElement
>>> SaltzmanMaaschElement('climate').q
2.5
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
x: Annotated[float, StateSpec()] = -0.2,
y: Annotated[float, StateSpec()] = 1.0,
z: Annotated[float, StateSpec()] = 0.0,
p: Annotated[float, ParameterSpec()] = 1.0,
q: Annotated[float, ParameterSpec()] = 2.5,
r: Annotated[float, ParameterSpec()] = 1.3,
s: Annotated[float, ParameterSpec()] = 0.6,
u: Annotated[float, ParameterSpec()] = 0.2,
nu: Annotated[float, ParameterSpec()] = 0.0,
t0: Annotated[float, ParameterSpec()] = 10.0,
) -> None:
super().__init__(label, visual=visual)
self.x = x
self.y = y
self.z = z
self.p = p
self.q = q
self.r = r
self.s = s
self.u = u
self.nu = nu
self.t0 = t0
def _get_orbital_forcing(self, s: SimpleNamespace) -> Expression:
"""Return the quasiperiodic orbital forcing, with the periods of precession and obliquity.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
forcing:
The orbital forcing.
"""
return cos(2 * pi * s.t0 * s.t / 22.1) - cos(2 * pi * s.t0 * s.t / 44.1)
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the feedbacks of ice volume, carbon dioxide, and deep ocean temperature, with orbital forcing.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The rate laws of the three anomalies.
"""
forcing = self._get_orbital_forcing(s)
return [
RateLaw(s.x, -s.x - s.y - s.u * s.z - s.nu * forcing),
RateLaw(s.y, -s.p * s.z + s.r * s.y - s.s * s.y**2 - s.y**3),
RateLaw(s.z, -s.q * (s.x + s.z)),
]
Equations
climate.x: diff(x_climate(t), t) == -nu_climate*(cos(0.09049773755656108*pi*t*t0_climate) - cos(0.045351473922902494*pi*t*t0_climate)) - u_climate*z_climate(t) - x_climate(t) - y_climate(t)
climate.y: diff(y_climate(t), t) == -s_climate*y_climate(t)^2 - y_climate(t)^3 + r_climate*y_climate(t) - p_climate*z_climate(t)
climate.z: diff(z_climate(t), t) == -q_climate*(x_climate(t) + z_climate(t))