Skip to content

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))