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