Skip to content

Zero-dimensional energy balance with ice albedo

The zero-dimensional energy balance of the Earth, bistable through the ice-albedo feedback.

  • Name: energy_balance
  • Field: physics/climate
  • Formalism: ordinary differential equations
  • References: Budyko 1969; Sellers 1969; Datseris and Parlitz 2022; Ghil and Lucarini 2020

Factory

@register_model(
    field='physics/climate',
    tags=('ode', 'bistability', 'ice-albedo feedback', 'energy balance'),
    references=('Budyko 1969', 'Sellers 1969', 'Datseris and Parlitz 2022', 'Ghil and Lucarini 2020'),
    defaults={'until': 500.0, 'dt': 5.0},
)
def energy_balance(
    T: float = 290.0,
    *,
    s: float = 1.0,
    epsilon: float = 0.65,
    sigma: float = 1.6e-10,
    albedo: float = 0.5,
    contrast: float = 0.2,
    T_ice: float = 263.0,
    width: float = 4.0,
) -> System:
    """Return the zero-dimensional energy balance of the Earth, bistable through the ice-albedo feedback.

    In units of the mean insolation, the global temperature follows `dT/dt = s (1 - a(T)) - sigma epsilon T^4`, with
    the albedo `a(T) = albedo - contrast tanh((T - T_ice) / width)`, high on an icy planet and low on a warm one. For
    the default parameters a warm climate near 286 K and a frozen climate near 232 K are both stable.

    Args:
        T:
            The initial global mean temperature in kelvin.

        s:
            The insolation relative to the present one.

        epsilon:
            The effective emissivity, lowered by the greenhouse effect.

        sigma:
            The Stefan-Boltzmann constant divided by the mean insolation.

        albedo:
            The albedo at the middle of the transition from ice to no ice.

        contrast:
            Half the difference between the albedos of a frozen and of a warm planet.

        T_ice:
            The temperature of the middle of the transition.

        width:
            The width of the transition in kelvin.

    Returns:
        system:
            The model, with the temperature `earth.T`.

    Examples:
        >>> from skcomplex.models.physics.climate import energy_balance
        >>> energy_balance(T=230.0).elements[0].T
        230.0
    """
    climate = IceAlbedoClimateElement(
        'earth',
        T=T,
        s=s,
        epsilon=epsilon,
        sigma=sigma,
        albedo=albedo,
        contrast=contrast,
        T_ice=T_ice,
        width=width,
    )
    return System(elements=[climate])

Components

class IceAlbedoClimateElement(Element):
    """Global climate of a zero-dimensional energy balance, bistable through the ice-albedo feedback.

    In units of the mean insolation, the global temperature follows `dT/dt = s (1 - a(T)) - sigma epsilon T^4`, with
    the albedo `a(T) = albedo - contrast tanh((T - T_ice) / width)`, high on an icy planet and low on a warm one.

    Args:
        label:
            The label of the climate.

        visual:
            How the element is drawn, or `None` for `default_visual_`.

        T:
            The initial global mean temperature in kelvin.

        s:
            The insolation relative to the present one.

        epsilon:
            The effective emissivity, lowered by the greenhouse effect.

        sigma:
            The Stefan-Boltzmann constant divided by the mean insolation.

        albedo:
            The albedo at the middle of the transition from ice to no ice.

        contrast:
            Half the difference between the albedos of a frozen and of a warm planet.

        T_ice:
            The temperature of the middle of the transition.

        width:
            The width of the transition in kelvin.

    Examples:
        >>> from skcomplex.models.physics.climate import IceAlbedoClimateElement
        >>> IceAlbedoClimateElement('earth').T_ice
        263.0
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        T: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 290.0,
        s: Annotated[float, ParameterSpec()] = 1.0,
        epsilon: Annotated[float, ParameterSpec()] = 0.65,
        sigma: Annotated[float, ParameterSpec()] = 1.6e-10,
        albedo: Annotated[float, ParameterSpec()] = 0.5,
        contrast: Annotated[float, ParameterSpec()] = 0.2,
        T_ice: Annotated[float, ParameterSpec()] = 263.0,
        width: Annotated[float, ParameterSpec()] = 4.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.T = T
        self.s = s
        self.epsilon = epsilon
        self.sigma = sigma
        self.albedo = albedo
        self.contrast = contrast
        self.T_ice = T_ice
        self.width = width

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the absorbed insolation, lowered by the albedo of the ice, and the outgoing radiation.

        Args:
            s:
                The namespace of the symbols the laws can use.

        Returns:
            laws:
                The rate law of the temperature.
        """
        return [
            RateLaw(
                s.T,
                s.s * (1 - (s.albedo - s.contrast * tanh((s.T - s.T_ice) / s.width))) - s.sigma * s.epsilon * s.T**4,
            ),
        ]

Equations

earth.T: diff(T_earth(t), t) == -epsilon_earth*sigma_earth*T_earth(t)^4 + (contrast_earth*tanh(-(T_ice_earth - T_earth(t))/width_earth) - albedo_earth + 1)*s_earth