Skip to content

Commit 6df3d91

Browse files
Merge pull request #1764 from CliMA/ne/ecs
Add equilibrium climate sensitivity test to longruns
2 parents 858d6bc + e108b94 commit 6df3d91

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed

.buildkite/longruns/pipeline.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,16 @@ steps:
188188

189189
steps:
190190

191+
- label: ":thermometer: Cess climate sensitivity approximation from p2K runs"
192+
key: "cess_from_p2k"
193+
command: >-
194+
julia --color=yes --project=experiments/ClimaEarth/
195+
test/cess_climate_sensitivity.jl
196+
output/amip_diagedmf_land_gpu
197+
output/amip_diagedmf_land_p2k_gpu
198+
output/ecs_analysis
199+
artifact_paths: "output/ecs_analysis/*"
200+
191201
# plot job performance history
192202
- label: ":chart_with_downwards_trend: build history"
193203
command:

test/cess_climate_sensitivity.jl

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Estimate climate sensitivity from a pair of AMIP runs: a control
2+
# run and a run with uniformly raised sea surface temperature (+2K SST). The difference
3+
# in time-averaged TOA outgoing fluxes (rsut, rlut) between the two runs is used to
4+
# compute the climate feedback parameter λ = ΔF_net / ΔTs, from which
5+
# ECS ≈ ERF_2xCO2 / λ is derived.
6+
#
7+
# Usage: julia --project=experiments/ClimaEarth/ cess_climate_sensitivity.jl \
8+
# <ctrl_atmos_dir> <p2k_atmos_dir> <output_dir>
9+
10+
import ClimaAnalysis
11+
import GeoMakie
12+
import CairoMakie
13+
import Dates
14+
15+
if length(ARGS) < 3
16+
error(
17+
"Usage: julia cess_climate_sensitivity.jl " *
18+
"<ctrl_atmos_dir> <p2k_atmos_dir> <output_dir>",
19+
)
20+
end
21+
22+
ctrl_dir = ClimaAnalysis.SimDir(ARGS[1])
23+
p2k_dir = ClimaAnalysis.SimDir(ARGS[2])
24+
output_dir = ARGS[3]
25+
26+
mkpath(output_dir)
27+
28+
# Remove first 3 months of each run as spinup
29+
const SPINUP_MONTHS = 3
30+
# Radiative forcing for a doubling of CO2, derived from the simplified
31+
# expression in Table 3 of Myhre et al. (1998, doi:10.1029/98GL01908):
32+
# ΔF = 5.35·ln(C/C₀) which gives 5.35·ln(2) ≈ 3.71 W m⁻²
33+
const ERF_2xCO2 = 3.7 # W m⁻²
34+
# SST perturbation applied in the p2k run
35+
const DELTA_SST = 2.0 # K
36+
37+
# Load a monthly-averaged TOA flux variable from simdir, remove spinup months,
38+
# and return the time-averaged field together with the dates of the averaging period.
39+
function load_and_average(simdir, short_name)
40+
var = ClimaAnalysis.get(simdir; short_name, reduction = "average", period = "1M")
41+
spinup_cutoff = SPINUP_MONTHS * 31 * 86400.0
42+
if ClimaAnalysis.times(var)[end] >= spinup_cutoff
43+
var = ClimaAnalysis.window(var, "time", left = spinup_cutoff)
44+
end
45+
return ClimaAnalysis.average_time(var)
46+
end
47+
48+
@info "Loading TOA fluxes"
49+
rsut_ctrl = load_and_average(ctrl_dir, "rsut")
50+
rlut_ctrl = load_and_average(ctrl_dir, "rlut")
51+
rsut_p2k = load_and_average(p2k_dir, "rsut")
52+
rlut_p2k = load_and_average(p2k_dir, "rlut")
53+
var_dates = ClimaAnalysis.dates(ClimaAnalysis.get(ctrl_dir, "rlut"))
54+
55+
# Compute differences (p2k - ctrl)
56+
delta_rsut = rsut_p2k - rsut_ctrl
57+
delta_rlut = rlut_p2k - rlut_ctrl
58+
59+
# Set metadata on difference vars so plot titles are informative
60+
delta_rsut.attributes["short_name"] = "delta_rsut"
61+
delta_rsut.attributes["long_name"] = "TOA upwelling SW flux change (+2K SST − control)"
62+
delta_rlut.attributes["short_name"] = "delta_rlut"
63+
delta_rlut.attributes["long_name"] = "TOA upwelling LW flux change (+2K SST − control)"
64+
65+
# Compute area-weighted global means of each flux change
66+
delta_rsut_global = ClimaAnalysis.weighted_average_lonlat(delta_rsut).data[]
67+
delta_rlut_global = ClimaAnalysis.weighted_average_lonlat(delta_rlut).data[]
68+
69+
# Net TOA outgoing radiation change: positive means more energy lost to space
70+
delta_toa = delta_rsut_global + delta_rlut_global
71+
72+
@info "Global mean TOA flux changes (p2K − control):"
73+
@info "delta_rsut = $(round(delta_rsut_global, digits = 3)) W m⁻²"
74+
@info "delta_rlut = $(round(delta_rlut_global, digits = 3)) W m⁻²"
75+
@info "delta_toa = $(round(delta_toa, digits = 3)) W m⁻²"
76+
77+
# Climate feedback parameter λ = delta_toa / delta_sst
78+
# ECS ≈ ERF_2xCO2 / λ
79+
λ = delta_toa / DELTA_SST
80+
ecs_estimate = ERF_2xCO2 / λ
81+
82+
@info "Climate sensitivity estimate:"
83+
@info "λ = delta_toa / delta_sst = $(round(λ, digits = 3)) W m⁻² K⁻¹"
84+
@info "Approximate ECS = ERF_2xCO2 / λ ≈ $(round(ecs_estimate, digits = 2)) K"
85+
86+
# Build figure title from the actual averaging period and spinup
87+
date_fmt = d -> Dates.format(d, "u yyyy")
88+
fig_title =
89+
"TOA flux change (+2K SST − control) | " *
90+
"averaging period: $(date_fmt(var_dates[1]))$(date_fmt(var_dates[end])) | " *
91+
"spinup: $SPINUP_MONTHS months"
92+
93+
fig = CairoMakie.Figure(; size = (700, 900))
94+
CairoMakie.Label(fig[0, 1], fig_title; tellwidth = false)
95+
ClimaAnalysis.Visualize.plot_bias_on_globe!(fig[1, 1], rsut_p2k, rsut_ctrl)
96+
ClimaAnalysis.Visualize.plot_bias_on_globe!(fig[2, 1], rlut_p2k, rlut_ctrl)
97+
output_path = joinpath(output_dir, "toa_flux_difference.png")
98+
CairoMakie.save(output_path, fig)
99+
@info "Saved plot to $output_path"

0 commit comments

Comments
 (0)