Milestone 3 - Project Results
Expected Results
This is an example for the expected results:
Solar Forcing vector
Spatial averages for every time step as a vector in a .txt file:
(Download solar_forcing_averages.txt)
Annual Temperature Plot
Calculated with the forward Euler method. Plot for the backward Euler is almost identical.

⚠ 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 3 can be downloaded here:
Scripts for Milestone 3
You can also check out our Julia and Python implementations of milestone 3 in this site.
Julia implementation of milestone 3
using LinearAlgebra
include("milestone1.jl")
include("milestone2.jl")
function calc_area(geo_dat)
nlatitude, nlongitude = size(geo_dat)
area = zeros(nlatitude)
delta_theta = pi / (nlatitude - 1)
# Poles
area[1] = area[end] = 0.5 * (1 - cos(0.5 * delta_theta))
# Inner cells
for j in 2:(nlatitude - 1)
area[j] = sin(0.5 * delta_theta) * sin(delta_theta * (j - 1)) / nlongitude
end
return area
end
function calc_mean(data, area)
nlatitude, nlongitude = size(data)
mean_data = area[1] * data[1, 1] + area[end] * data[end, end]
for i in 2:(nlatitude - 1)
for j in 1:nlongitude
mean_data += area[i] * data[i, j]
end
end
return mean_data
end
function calc_radiative_cooling_co2(co2_concentration, co2_concentration_base=315.0,
radiative_cooling_base=210.3)
return radiative_cooling_base - 5.35 * log(co2_concentration / co2_concentration_base)
end
function timestep_euler_forward(mean_temperature, t, delta_t, mean_heat_capacity,
mean_solar_forcing, radiative_cooling)
# Rearrange the energy balance equation to
# d/dt T = f(T, t),
# where f(T, t) = (S_sol(t) - A - BT) / C.
# This function is the right-hand side f,
# where t is not the time but the array index for the corresponding time.
function rhs(mean_temp, t_)
return (mean_solar_forcing[t_] - radiative_cooling - 2.15 * mean_temp) /
mean_heat_capacity
end
# Calculate T_t = T_{t-1} + delta_t * rhs(T_{t-1}, t-1) (forward Euler).
if t > 1
return mean_temperature[t - 1] + delta_t * rhs(mean_temperature[t - 1], t - 1)
else
# In the first iteration, we access the last entry of mean_temperature.
# Therefore, we start in each iteration with the last temperature of the previous iteration.
ntimesteps = length(mean_temperature)
return mean_temperature[end] + delta_t * rhs(mean_temperature[end], ntimesteps)
end
end
function compute_equilibrium(timestep_function, mean_heat_capacity, mean_solar_forcing,
radiative_cooling;
max_iterations=100, rel_error=2e-5, verbose=true)
# Number of time steps per year.
ntimesteps = length(mean_solar_forcing)
# Step size
delta_t = 1 / ntimesteps
# We start with a constant area-mean temperature of 0 throughout the year.
mean_temperature = zeros(ntimesteps)
year_avg_temperature = 0
# Mean temperature from the previous iteration to approximate the error.
old_mean_temperature = copy(mean_temperature)
for i in 1:max_iterations
for t in 1:ntimesteps
mean_temperature[t] = timestep_function(mean_temperature, t, delta_t,
mean_heat_capacity, mean_solar_forcing,
radiative_cooling)
end
year_avg_temperature = sum(mean_temperature) / ntimesteps
verbose &&
println("Average annual temperature in iteration $i is $year_avg_temperature.")
if norm(mean_temperature - old_mean_temperature) < rel_error
# We can assume that the error is sufficiently small now.
verbose && println("Equilibrium reached!")
break
else
# Note that we need to write the values from mean_temperature to old_mean_temperature
# in order to get two arrays with the same values.
# We can't omit the [:] or we would only have one array with two pointers to it.
old_mean_temperature[:] = mean_temperature
end
end
return mean_temperature, year_avg_temperature
end
function timestep_euler_backward(mean_temperature, t, delta_t, mean_heat_capacity,
mean_solar_forcing, radiative_cooling)
# This time, we have to solve the equation
# T_t = T_{t-1} + delta_t * f(T_t, t),
# where f(T, t) = (S_sol(t) - A - BT) / C.
# For this, we have to separate the terms that depend on T and the source terms that only depend on t.
# Solving for T_t yields
# T_t = (T_{t-1} + delta_t * (S_sol[t] - A) / C) / (1 + delta_t * B / C).
# Note that in the first iteration, we access mean_temperature[-1], which is the last entry.
# Therefore, we start in each iteration with the last temperature of the previous iteration.
source_terms = (mean_solar_forcing[t] - radiative_cooling) / mean_heat_capacity
if t > 1
previous_mean_temperature = mean_temperature[t - 1]
else
previous_mean_temperature = mean_temperature[end]
end
return (previous_mean_temperature + delta_t * source_terms) /
(1 + delta_t * 2.15 / mean_heat_capacity)
end
function plot_annual_temperature(annual_temperature, average_temperature, title)
ntimesteps = length(annual_temperature)
labels = ["March", "June", "September", "December", "March"]
p = plot(average_temperature * ones(ntimesteps), label="average temperature",
xlims=(1, ntimesteps), xticks=(LinRange(1, ntimesteps, 5), labels),
ylabel="surface temperature [°C]", title=title)
plot!(p, annual_temperature, label="annual temperature")
return p
end
# Run code
function milestone3()
geo_dat_ = read_geography(joinpath(@__DIR__, "input", "The_World128x65.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 = calc_mean(albedo, area_)
print("Mean albedo = $mean_albedo")
mean_heat_capacity_ = calc_mean(heat_capacity, area_)
print("Mean heat capacity = $mean_heat_capacity_")
ntimesteps_ = length(true_longitude)
mean_solar_forcing_ = [calc_mean(solar_forcing[:, :, t], area_) for t in 1:ntimesteps_]
co2_ppm = 315.0
radiative_cooling_ = calc_radiative_cooling_co2(co2_ppm)
# Compute and plot temperature with Euler forward
(annual_temperature_,
average_temperature_) = compute_equilibrium(timestep_euler_forward,
mean_heat_capacity_,
mean_solar_forcing_,
radiative_cooling_)
plot_forward = plot_annual_temperature(annual_temperature_, average_temperature_,
"Annual temperature with CO2 = $co2_ppm [ppm]")
# Compute and plot temperature with Euler backward
(annual_temperature_,
average_temperature_) = compute_equilibrium(timestep_euler_backward,
mean_heat_capacity_,
mean_solar_forcing_,
radiative_cooling_)
plot_backward = plot_annual_temperature(annual_temperature_, average_temperature_,
"Annual temperature with CO2 = $co2_ppm [ppm]")
# Show both plots
display(plot_forward)
display(plot_backward)
return plot_forward, plot_backward
end Python implementation of milestone 3
import matplotlib.pyplot as plt
import numpy as np
from milestone1 import read_geography
from milestone2 import calc_albedo, calc_heat_capacity, calc_solar_forcing, read_true_longitude
def calc_area(geo_dat):
nlatitude, nlongitude = geo_dat.shape
area = np.zeros(nlatitude, dtype=np.float64)
delta_theta = np.pi / (nlatitude - 1)
# Poles
area[0] = area[-1] = 0.5 * (1 - np.cos(0.5 * delta_theta))
# Inner cells
for j in range(1, nlatitude - 1):
area[j] = np.sin(0.5 * delta_theta) * np.sin(delta_theta * j) / nlongitude
return area
def calc_mean(data, area):
nlatitude, nlongitude = data.shape
mean_data = area[0] * data[0, 0] + area[-1] * data[-1, -1]
for i in range(1, nlatitude - 1):
for j in range(nlongitude):
mean_data += area[i] * data[i, j]
return mean_data
def calc_radiative_cooling_co2(co2_concentration, co2_concentration_base=315.0,
radiative_cooling_base=210.3):
return radiative_cooling_base - 5.35 * np.log(co2_concentration / co2_concentration_base)
def timestep_euler_forward(mean_temperature, t, delta_t, mean_heat_capacity, mean_solar_forcing, radiative_cooling):
# Rearrange the energy balance equation to
# d/dt T = f(T, t),
# where f(T, t) = (S_sol(t) - A - BT) / C.
# This function is the right-hand side f,
# where t is not the time but the array index for the corresponding time.
def rhs(mean_temp, t_):
return (mean_solar_forcing[t_] - radiative_cooling - 2.15 * mean_temp) / mean_heat_capacity
# Calculate T_t = T_{t-1} + delta_t * rhs(T_{t-1}, t-1) (forward Euler).
# Note that in the first iteration, we access mean_temperature[-1], which is the last entry.
# Therefore, we start in each iteration with the last temperature of the previous iteration.
return mean_temperature[t - 1] + delta_t * rhs(mean_temperature[t - 1], t - 1)
def compute_equilibrium(timestep_function, mean_heat_capacity, mean_solar_forcing, radiative_cooling,
max_iterations=100, rel_error=2e-5, verbose=True):
# Number of time steps per year.
ntimesteps = len(mean_solar_forcing)
# Step size
delta_t = 1 / ntimesteps
# We start with a constant area-mean temperature of 0 throughout the year.
mean_temperature = np.zeros(ntimesteps)
year_avg_temperature = 0
# Mean temperature from the previous iteration to approximate the error.
old_mean_temperature = np.copy(mean_temperature)
for i in range(max_iterations):
for t in range(ntimesteps):
mean_temperature[t] = timestep_function(mean_temperature, t, delta_t,
mean_heat_capacity, mean_solar_forcing, radiative_cooling)
year_avg_temperature = np.sum(mean_temperature) / ntimesteps
verbose and print(f"Average annual temperature in iteration {i} is {year_avg_temperature}.")
if np.linalg.norm(mean_temperature - old_mean_temperature) < rel_error:
# We can assume that the error is sufficiently small now.
verbose and print("Equilibrium reached!")
break
else:
# Note that we need to write the values from mean_temperature to old_mean_temperature
# in order to get two arrays with the same values.
# We can't omit the [:] or we would only have one array with two pointers to it.
old_mean_temperature[:] = mean_temperature
return mean_temperature, year_avg_temperature
def timestep_euler_backward(mean_temperature, t, delta_t, mean_heat_capacity, mean_solar_forcing, radiative_cooling):
# This time, we have to solve the equation
# T_t = T_{t-1} + delta_t * f(T_t, t),
# where f(T, t) = (S_sol(t) - A - BT) / C.
# For this, we have to separate the terms that depend on T and the source terms that only depend on t.
# Solving for T_t yields
# T_t = (T_{t-1} + delta_t * (S_sol[t] - A) / C) / (1 + delta_t * B / C).
# Note that in the first iteration, we access mean_temperature[-1], which is the last entry.
# Therefore, we start in each iteration with the last temperature of the previous iteration.
source_terms = (mean_solar_forcing[t] - radiative_cooling) / mean_heat_capacity
return (mean_temperature[t - 1] + delta_t * source_terms) / (1 + delta_t * 2.15 / mean_heat_capacity)
def plot_annual_temperature(annual_temperature, average_temperature, title):
fig, ax = plt.subplots()
ntimesteps = len(annual_temperature)
plt.plot(average_temperature * np.ones(ntimesteps), label="average temperature")
plt.plot(annual_temperature, label="annual temperature")
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(title)
plt.legend(loc="upper right")
plt.tight_layout()
plt.show()
# Run code
if __name__ == "__main__":
geo_dat_ = read_geography("input/The_World128x65.dat")
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 = calc_mean(albedo, area_)
print(f"Mean albedo = {mean_albedo}")
mean_heat_capacity_ = calc_mean(heat_capacity, area_)
print(f"Mean heat capacity = {mean_heat_capacity_}")
ntimesteps_ = len(true_longitude)
mean_solar_forcing_ = [calc_mean(solar_forcing[:, :, t], area_) for t in range(ntimesteps_)]
co2_ppm = 315.0
radiative_cooling_ = calc_radiative_cooling_co2(co2_ppm)
# Compute and plot temperature with Euler forward
annual_temperature_, average_temperature_ = compute_equilibrium(timestep_euler_forward,
mean_heat_capacity_, mean_solar_forcing_,
radiative_cooling_)
plot_annual_temperature(annual_temperature_, average_temperature_, f"Annual temperature with CO2 = {co2_ppm} [ppm]")
# Compute and plot temperature with Euler backward
annual_temperature_, average_temperature_ = compute_equilibrium(timestep_euler_backward,
mean_heat_capacity_, mean_solar_forcing_,
radiative_cooling_)
plot_annual_temperature(annual_temperature_, average_temperature_, f"Annual temperature with CO2 = {co2_ppm} [ppm]") 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.