Milestone 4 - Project Results

Expected Results

This is an example for the expected results:

Mean Temperature Plot

Plot of the mean and average temperature calculated by averaging over the regions first and then using the 0D-EBM.

Mean temperature plot

Mean Temperature Plot using Pointwise EBM

Plot of the mean and average temperature calculated by using a 0D-EBM for every point of the discretization and then averaging over the results.

Pointwise temperature plot

Cologne Temperature

Cologne temperature calculated via the pointwise 0D-EBM

Cologne temperature plot

Annual Temperature Animation

Annual temperature with the pointwise 0D-EBM:

Annual temperature animation

⚠ Warning!
We strongly suggest to first work on the solutions on your own/within your group without checking directly for the reference solutions.

Files Download

The Julia and Python implementations of milestone 4 can be downloaded here:

Scripts for Milestone 4

You can also check out our Julia and Python implementations of milestone 4 in this site.

Julia implementation of milestone 4

include("milestone1.jl")
include("milestone2.jl")
include("milestone3.jl")

function calc_mean_north(data, area)
    nlatitude, nlongitude = size(data)
    j_equator = Int((nlatitude - 1) / 2) + 1

    # North Pole
    mean_data = area[1] * data[1, 1]

    # Inner nodes
    for j in 2:(j_equator - 1)
        for i in 1:nlongitude
            mean_data += area[j] * data[j, i]
        end
    end

    # Equator
    for i in 1:nlongitude
        mean_data += 0.5 * area[j_equator] * data[j_equator, i]
    end

    return 2 * mean_data
end

function calc_mean_south(data, area)
    nlatitude, nlongitude = size(data)
    j_equator = Int((nlatitude - 1) / 2) + 1

    # South Pole
    mean_data = area[end] * data[end, end]

    # Inner nodes
    for j in (j_equator + 1):(nlatitude - 1)
        for i in 1:nlongitude
            mean_data += area[j] * data[j, i]
        end
    end

    # Equator
    for i in 1:nlongitude
        mean_data += 0.5 * area[j_equator] * data[j_equator, i]
    end

    return 2 * mean_data
end

function plot_annual_temperature_north_south(annual_temperature_north,
                                             annual_temperature_south,
                                             annual_temperature_total,
                                             average_temperature_north,
                                             average_temperature_south,
                                             average_temperature_total)
    ntimesteps = length(annual_temperature_total)
    labels = ["March", "June", "September", "December", "March"]

    p = plot(average_temperature_total * ones(ntimesteps),
             label="average temperature (total)",
             xlims=(1, ntimesteps), xticks=(LinRange(1, ntimesteps, 5), labels),
             ylabel="surface temperature [°C]",
             title="Annual temperature with CO2 = 315 [ppm]")
    plot!(p, average_temperature_north * ones(ntimesteps),
          label="average temperature (north)")
    plot!(p, average_temperature_south * ones(ntimesteps),
          label="average temperature (south)")
    plot!(p, annual_temperature_total, label="temperature (total)")
    plot!(p, annual_temperature_north, label="temperature (north)")
    plot!(p, annual_temperature_south, label="temperature (south)")

    return p
end

function plot_temperature(temperature, geo_dat, timestep)
    vmin = minimum(temperature)
    vmax = -vmin # We want to have 0°C in the center.

    nlatitude, nlongitude = size(temperature)
    x, y = robinson_projection(nlatitude, nlongitude)

    ntimesteps = size(temperature, 3)
    day = (round(Int, (timestep - 1) / ntimesteps * 365) + 80) % 365
    p = contourf(x, y, temperature[:, :, timestep],
                 clims=(vmin, vmax),
                 levels=LinRange(vmin, vmax, 200),
                 aspect_ratio=1,
                 title="Temperature for Day $day",
                 c=:seismic,
                 colorbar_title="temperature [°C]",
                 axis=([], false),
                 dpi=300)

    # Add contour lines
    contour!(p, x, y, geo_dat, levels=[0.5, 1.5, 2.5, 3.5, 6.5],
             color=[:black], linewidth=0.6)

    return p
end

# Run code
function milestone4()
    geo_dat = read_geography(joinpath(@__DIR__, "input", "The_World128x65.dat"))
    nlatitude, nlongitude = size(geo_dat)

    albedo = calc_albedo(geo_dat)
    heat_capacity = calc_heat_capacity(geo_dat)

    # Compute solar forcing
    true_longitude = read_true_longitude(joinpath(@__DIR__, "input", "True_Longitude.dat"))
    solar_forcing = calc_solar_forcing(albedo, true_longitude)

    # Compute area-mean quantities
    area = calc_area(geo_dat)

    mean_albedo_north = calc_mean_north(albedo, area)
    print("Mean albedo north = $mean_albedo_north")
    mean_albedo_south = calc_mean_south(albedo, area)
    print("Mean albedo south = $mean_albedo_south")
    mean_albedo_total = calc_mean(albedo, area)
    print("Mean albedo total = $mean_albedo_total")

    mean_heat_capacity_north = calc_mean_north(heat_capacity, area)
    print("Mean heat capacity north = $mean_heat_capacity_north")
    mean_heat_capacity_south = calc_mean_south(heat_capacity, area)
    print("Mean heat capacity south = $mean_heat_capacity_south")
    mean_heat_capacity_total = calc_mean(heat_capacity, area)
    print("Mean heat capacity total = $mean_heat_capacity_total")

    ntimesteps = length(true_longitude)
    mean_solar_forcing_north = [calc_mean_north(solar_forcing[:, :, t], area)
                                for t in 1:ntimesteps]
    mean_solar_forcing_south = [calc_mean_south(solar_forcing[:, :, t], area)
                                for t in 1:ntimesteps]
    mean_solar_forcing_total = [calc_mean(solar_forcing[:, :, t], area)
                                for t in 1:ntimesteps]

    co2_ppm = 315.0
    radiative_cooling = calc_radiative_cooling_co2(co2_ppm)

    # Compute equilibrium for all three means
    (annual_temperature_north_,
     average_temperature_north_) = compute_equilibrium(timestep_euler_forward,
                                                       mean_heat_capacity_north,
                                                       mean_solar_forcing_north,
                                                       radiative_cooling)
    (annual_temperature_south_,
     average_temperature_south_) = compute_equilibrium(timestep_euler_forward,
                                                       mean_heat_capacity_south,
                                                       mean_solar_forcing_south,
                                                       radiative_cooling)
    (annual_temperature_total_,
     average_temperature_total_) = compute_equilibrium(timestep_euler_forward,
                                                       mean_heat_capacity_total,
                                                       mean_solar_forcing_total,
                                                       radiative_cooling)

    plot_mean = plot_annual_temperature_north_south(annual_temperature_north_,
                                                    annual_temperature_south_,
                                                    annual_temperature_total_,
                                                    average_temperature_north_,
                                                    average_temperature_south_,
                                                    average_temperature_total_)

    # Calculate annual temperature for every grid point
    annual_temperature_pointwise = Array{Float64, 3}(undef, nlatitude, nlongitude,
                                                     ntimesteps)
    for j in 1:nlongitude, i in 1:nlatitude
        # I/O in Julia is pretty slow, so this takes forever with `verbose=true`.
        (annual_temperature,
         _) = compute_equilibrium(timestep_euler_forward,
                                  heat_capacity[i, j],
                                  solar_forcing[i, j, :],
                                  radiative_cooling,
                                  verbose=false)

        annual_temperature_pointwise[i, j, :] = annual_temperature
    end

    # Area mean of pointwise annual temperature
    annual_mean_temperature_north = [calc_mean_north(annual_temperature_pointwise[:, :, t],
                                                     area)
                                     for t in 1:ntimesteps]
    annual_mean_temperature_south = [calc_mean_south(annual_temperature_pointwise[:, :, t],
                                                     area)
                                     for t in 1:ntimesteps]
    annual_mean_temperature_total = [calc_mean(annual_temperature_pointwise[:, :, t], area)
                                     for t in 1:ntimesteps]

    average_temperature_north_ = sum(annual_mean_temperature_north) / ntimesteps
    average_temperature_south_ = sum(annual_mean_temperature_south) / ntimesteps
    average_temperature_total_ = sum(annual_mean_temperature_total) / ntimesteps

    plot_pointwise = plot_annual_temperature_north_south(annual_mean_temperature_north,
                                                         annual_mean_temperature_south,
                                                         annual_mean_temperature_total,
                                                         average_temperature_north_,
                                                         average_temperature_south_,
                                                         average_temperature_total_)

    # Compute temperature in Cologne.
    # Cologne lies about halfway between these two grid points.
    annual_temperature_cologne = (annual_temperature_pointwise[15, 68, :] +
                                  annual_temperature_pointwise[15, 69, :]) / 2
    average_temperature_cologne = sum(annual_temperature_cologne) / ntimesteps

    plot_cologne = plot_annual_temperature(annual_temperature_cologne,
                                           average_temperature_cologne,
                                           "Annual temperature with CO2 = $co2_ppm [ppm] in Cologne")

    # Animate annual temperature
    anim = @animate for ts in 1:ntimesteps
        plot_temperature(annual_temperature_pointwise, geo_dat, ts)
    end

    plot_temperature_day_80 = plot_temperature(annual_temperature_pointwise, geo_dat, 1)
    gif_annual_temperature = gif(anim, joinpath(@__DIR__, "annual_temperature.gif"), fps=7)

    # Show all plots and the animation
    display(plot_mean)
    display(plot_pointwise)
    display(plot_cologne)
    display(gif_annual_temperature)

    return plot_mean, plot_pointwise, plot_cologne, plot_temperature_day_80,
           gif_annual_temperature
end

Python implementation of milestone 4

import os

import imageio
import matplotlib.colors as colors
import matplotlib.pyplot as plt
import numpy as np

from milestone1 import read_geography, robinson_projection
from milestone2 import read_true_longitude
from milestone3 import calc_area, calc_albedo, calc_solar_forcing, calc_mean, calc_heat_capacity, \
    calc_radiative_cooling_co2, compute_equilibrium, timestep_euler_forward, plot_annual_temperature


def calc_mean_north(data, area):
    nlatitude, nlongitude = data.shape
    j_equator = int((nlatitude - 1) / 2)

    # North Pole
    mean_data = area[0] * data[0, 0]

    # Inner nodes
    for j in range(1, j_equator):
        for i in range(nlongitude):
            mean_data += area[j] * data[j, i]

    # Equator
    for i in range(nlongitude):
        mean_data += 0.5 * area[j_equator] * data[j_equator, i]

    return 2 * mean_data


def calc_mean_south(data, area):
    nlatitude, nlongitude = data.shape
    j_equator = int((nlatitude - 1) / 2)

    # South Pole
    mean_data = area[-1] * data[-1, -1]

    # Inner nodes
    for j in range(j_equator + 1, nlatitude - 1):
        for i in range(nlongitude):
            mean_data += area[j] * data[j, i]

    # Equator
    for i in range(nlongitude):
        mean_data += 0.5 * area[j_equator] * data[j_equator, i]

    return 2 * mean_data


def plot_annual_temperature_north_south(annual_temperature_north, annual_temperature_south, annual_temperature_total,
                                        average_temperature_north, average_temperature_south,
                                        average_temperature_total):
    fig, ax = plt.subplots()

    ntimesteps = len(annual_temperature_total)
    plt.plot(average_temperature_total * np.ones(ntimesteps), label="average temperature (total)")
    plt.plot(average_temperature_north * np.ones(ntimesteps), label="average temperature (north)")
    plt.plot(average_temperature_south * np.ones(ntimesteps), label="average temperature (south)")
    plt.plot(annual_temperature_total, label="temperature (total)")
    plt.plot(annual_temperature_north, label="temperature (north)")
    plt.plot(annual_temperature_south, label="temperature (south)")

    plt.xlim((0, ntimesteps - 1))
    labels = ["March", "June", "September", "December", "March"]
    plt.xticks(np.linspace(0, ntimesteps - 1, 5), labels)
    ax.set_ylabel("surface temperature [°C]")
    plt.grid()
    plt.title(f"Annual temperature with CO2 = 315 [ppm]")
    plt.legend(loc="upper right")

    plt.tight_layout()
    plt.show()


# Similar to plot_robinson_projection in MS1, but we also add contour lines
def plot_robinson_projection_with_lines(data, geo_dat, title, **kwargs):
    # Get the coordinates for the Robinson projection.
    nlatitude, nlongitude = data.shape
    x, y = robinson_projection(nlatitude, nlongitude)

    # Start plotting.
    fig, ax = plt.subplots()

    # Create contour plot of geography information against x and y.
    im = ax.contourf(x, y, data, **kwargs)

    # Add contour lines with levels from MS1
    levels = [0.5, 1.5, 2.5, 3.5, 6.5]
    ax.contour(x, y, geo_dat, colors="black", linewidths=0.6, levels=levels, linestyles="solid")

    plt.title(title)
    ax.set_aspect("equal")

    # Remove axes and ticks.
    plt.xticks([])
    plt.yticks([])
    ax.spines["top"].set_visible(False)
    ax.spines["right"].set_visible(False)
    ax.spines["bottom"].set_visible(False)
    ax.spines["left"].set_visible(False)

    # Colorbar with the same height as the plot. Code copied from
    # https://stackoverflow.com/a/18195921
    # create an axes on the right side of ax. The width of cax will be 5%
    # of ax and the padding between cax and ax will be fixed at 0.05 inch.
    from mpl_toolkits.axes_grid1 import make_axes_locatable
    divider = make_axes_locatable(ax)
    cax = divider.append_axes("right", size="5%", pad=0.05)
    cbar = plt.colorbar(im, cax=cax)

    return cbar


def plot_temperature(temperature, geo_dat, timestep, show_plot=False):
    vmin = np.amin(temperature)
    vmax = np.amax(temperature)
    levels = np.linspace(vmin, vmax, 200)
    norm = colors.TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax)

    # Reuse plotting function from milestone 1.
    ntimesteps = temperature.shape[2]
    day = (np.int_(timestep / ntimesteps * 365) + 80) % 365
    cbar = plot_robinson_projection_with_lines(temperature[:, :, timestep], geo_dat,
                                               f"Temperature for Day {day}",
                                               levels=levels, cmap="seismic",
                                               vmin=vmin, vmax=vmax, norm=norm)
    cbar.set_label("surface temperature [°C]")

    # Adjust size of plot to viewport to prevent clipping of the legend.
    plt.tight_layout()

    filename = f"temperature_{timestep}.png"
    plt.savefig(filename, dpi=300)
    if show_plot:
        plt.show()
    plt.close()

    return filename


# Run code
if __name__ == '__main__':
    geo_dat_ = read_geography("input/The_World128x65.dat")
    nlatitude_, nlongitude_ = geo_dat_.shape

    albedo = calc_albedo(geo_dat_)
    heat_capacity = calc_heat_capacity(geo_dat_)

    # Compute solar forcing
    true_longitude = read_true_longitude("input/True_Longitude.dat")
    solar_forcing = calc_solar_forcing(albedo, true_longitude)

    # Compute area-mean quantities
    area_ = calc_area(geo_dat_)

    mean_albedo_north = calc_mean_north(albedo, area_)
    print(f"Mean albedo north = {mean_albedo_north}")
    mean_albedo_south = calc_mean_south(albedo, area_)
    print(f"Mean albedo south = {mean_albedo_south}")
    mean_albedo_total = calc_mean(albedo, area_)
    print(f"Mean albedo total = {mean_albedo_total}")

    mean_heat_capacity_north = calc_mean_north(heat_capacity, area_)
    print(f"Mean heat capacity north = {mean_heat_capacity_north}")
    mean_heat_capacity_south = calc_mean_south(heat_capacity, area_)
    print(f"Mean heat capacity south = {mean_heat_capacity_south}")
    mean_heat_capacity_total = calc_mean(heat_capacity, area_)
    print(f"Mean heat capacity total = {mean_heat_capacity_total}")

    ntimesteps_ = len(true_longitude)
    mean_solar_forcing_north = [calc_mean_north(solar_forcing[:, :, t], area_) for t in range(ntimesteps_)]
    mean_solar_forcing_south = [calc_mean_south(solar_forcing[:, :, t], area_) for t in range(ntimesteps_)]
    mean_solar_forcing_total = [calc_mean(solar_forcing[:, :, t], area_) for t in range(ntimesteps_)]

    co2_ppm = 315.0
    radiative_cooling = calc_radiative_cooling_co2(co2_ppm)

    # Compute equilibrium for all three means
    annual_temperature_north_, average_temperature_north_ = compute_equilibrium(timestep_euler_forward,
                                                                                mean_heat_capacity_north,
                                                                                mean_solar_forcing_north,
                                                                                radiative_cooling)
    annual_temperature_south_, average_temperature_south_ = compute_equilibrium(timestep_euler_forward,
                                                                                mean_heat_capacity_south,
                                                                                mean_solar_forcing_south,
                                                                                radiative_cooling)
    annual_temperature_total_, average_temperature_total_ = compute_equilibrium(timestep_euler_forward,
                                                                                mean_heat_capacity_total,
                                                                                mean_solar_forcing_total,
                                                                                radiative_cooling)

    plot_annual_temperature_north_south(annual_temperature_north_, annual_temperature_south_, annual_temperature_total_,
                                        average_temperature_north_, average_temperature_south_,
                                        average_temperature_total_)

    # Calculate annual temperature for every grid point
    def annual_temp(i, j):
        annual_temperature, _ = compute_equilibrium(timestep_euler_forward, heat_capacity[i, j],
                                                    solar_forcing[i, j, :], radiative_cooling,
                                                    verbose=False)

        return annual_temperature

    annual_temperature_pointwise = np.array(
        [[annual_temp(i, j) for j in range(nlongitude_)] for i in range(nlatitude_)])

    # Area mean of pointwise annual temperature
    annual_mean_temperature_north = [calc_mean_north(annual_temperature_pointwise[:, :, t], area_)
                                     for t in range(ntimesteps_)]
    annual_mean_temperature_south = [calc_mean_south(annual_temperature_pointwise[:, :, t], area_)
                                     for t in range(ntimesteps_)]
    annual_mean_temperature_total = [calc_mean(annual_temperature_pointwise[:, :, t], area_)
                                     for t in range(ntimesteps_)]

    average_temperature_north_ = np.sum(annual_mean_temperature_north) / ntimesteps_
    average_temperature_south_ = np.sum(annual_mean_temperature_south) / ntimesteps_
    average_temperature_total_ = np.sum(annual_mean_temperature_total) / ntimesteps_

    plot_annual_temperature_north_south(annual_mean_temperature_north, annual_mean_temperature_south,
                                        annual_mean_temperature_total, average_temperature_north_,
                                        average_temperature_south_, average_temperature_total_)

    # Compute temperature in Cologne.
    # Cologne lies about halfway between these two grid points.
    annual_temperature_cologne = (annual_temperature_pointwise[14, 67, :] +
                                  annual_temperature_pointwise[14, 68, :]) / 2
    average_temperature_cologne = np.sum(annual_temperature_cologne) / ntimesteps_

    plot_annual_temperature(annual_temperature_cologne, average_temperature_cologne,
                            f"Annual temperature with CO2 = {co2_ppm} [ppm] in Cologne")

    # Plot temperature for each time step
    filenames = []
    for ts in range(ntimesteps_):
        filename_ = plot_temperature(annual_temperature_pointwise, geo_dat_, ts, show_plot=False)
        filenames.append(filename_)

    # Build GIF
    frames = [imageio.v3.imread(filename_) for filename_ in filenames]
    imageio.mimsave("annual_temperature.gif", frames)

    # Remove files
    for filename_ in set(filenames):
        os.remove(filename_)

Created by Gregor Gassner and Andrés Rueda-Ramírez with contributions by Simone Chiocchetti, Daniel Bach, Sophia Horak, Philipp Baasch, Benjamin Bolm, Erik Faulhaber, and Luca Sommer. Last modified: April 02, 2026. Website built with Franklin.jl and the Julia programming language.