diff --git a/.buildkite/hierarchies/pipeline.yml b/.buildkite/hierarchies/pipeline.yml index 507a51f669..e4f44c9aca 100644 --- a/.buildkite/hierarchies/pipeline.yml +++ b/.buildkite/hierarchies/pipeline.yml @@ -1,7 +1,7 @@ agents: queue: new-central slurm_time: 24:00:00 - modules: climacommon/2024_03_18 + modules: climacommon/2024_05_27 env: JULIA_LOAD_PATH: "${JULIA_LOAD_PATH}:${BUILDKITE_BUILD_CHECKOUT_PATH}/.buildkite" @@ -68,6 +68,16 @@ steps: slurm_gpus: 1 modules: common + - label: "Clima: GPU ClimaCoupler Cloudless Aquaplanet" + command: + - "julia --color=yes --project=experiments/ClimaEarth/ experiments/ClimaEarth/run_cloudless_aquaplanet.jl" + artifact_paths: "cloudless_aquaplanet/cloudless_aquaplanet/clima_atmos/*" + agents: + queue: clima + slurm_mem: 20GB + slurm_gpus: 1 + modules: common + - label: "Clima: GPU ClimaCoupler Cloudy Aquaplanet" command: - "julia --color=yes --project=experiments/ClimaEarth/ experiments/ClimaEarth/run_cloudy_aquaplanet.jl" diff --git a/.buildkite/longruns/pipeline.yml b/.buildkite/longruns/pipeline.yml index 30b533e19f..f3bafa2493 100644 --- a/.buildkite/longruns/pipeline.yml +++ b/.buildkite/longruns/pipeline.yml @@ -1,7 +1,7 @@ agents: queue: new-central slurm_time: 24:00:00 - modules: climacommon/2024_04_30 + modules: climacommon/2024_05_27 env: JULIA_LOAD_PATH: "${JULIA_LOAD_PATH}:${BUILDKITE_BUILD_CHECKOUT_PATH}/.buildkite" diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 17d4f8b0ed..3c7b0bc26f 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -1,7 +1,7 @@ agents: queue: new-central slurm_time: 4:00:00 - modules: climacommon/2024_04_30 + modules: climacommon/2024_05_27 env: JULIA_LOAD_PATH: "${JULIA_LOAD_PATH}:${BUILDKITE_BUILD_CHECKOUT_PATH}/.buildkite" @@ -397,6 +397,16 @@ steps: agents: slurm_mem: 20GB + - label: ":construction: Cloudless Aquaplanet" + key: "cloudless_aquaplanet" + command: + - sed 's/t_end = "1000days"/t_end = "1days"/' experiments/ClimaEarth/run_cloudless_aquaplanet.jl > experiments/ClimaEarth/run_cloudless_aquaplanet_short.jl + - "julia --color=yes --project=experiments/ClimaEarth/ experiments/ClimaEarth/run_cloudless_aquaplanet_short.jl" + artifact_paths: "cloudless_aquaplanet/cloudless_aquaplanet/clima_atmos/*" + + agents: + slurm_mem: 20GB + - label: ":construction: Cloudy Aquaplanet" key: "cloudy_aquaplanet" command: @@ -417,7 +427,14 @@ steps: agents: slurm_mem: 20GB - + - wait + - label: ":construction: Hierarchy plots" + key: "hierarchy_plots" + command: + - "julia --color=yes --project=experiments/ClimaEarth/ experiments/ClimaEarth/hierarchy/climate_plots.jl" + artifact_paths: "paper_figs/*" + agents: + slurm_mem: 20GB - group: "GPU integration tests" steps: diff --git a/experiments/ClimaEarth/hierarchy/README.md b/experiments/ClimaEarth/hierarchy/README.md new file mode 100644 index 0000000000..024126059a --- /dev/null +++ b/experiments/ClimaEarth/hierarchy/README.md @@ -0,0 +1,16 @@ +# Hierarchy Experiments for Global Climate Models + +This directory contains a series of experiments that demonstrate the use of hierarchical modeling using ClimaEarth, with a focus on the atmospheric component. The hierarchy spans from a simple dry atmosphere to a more complex moist atmosphere with clouds and an Earth-like surface. The experiments are designed to be run in sequence, with each experiment building on the previous one. Each experiment is a self-contained run script that contains all necessary configurations. + +## Experiments +- Dry Held-Suarez +- Moist Held-Suarez +- Cloudless Aquaplanet +- Cloudy Aquaplanet +- Cloudy Slabplanet + +## Postprocessing +We provide a simple postprocessing script `climate_plots.jl` that can be used to visualize the results of each experiment, with some helper functions in `plot_helper.jl`. + +## Associated publication +- (in preparation) diff --git a/experiments/ClimaEarth/hierarchy/climate_plots.jl b/experiments/ClimaEarth/hierarchy/climate_plots.jl new file mode 100644 index 0000000000..b2b7029dd6 --- /dev/null +++ b/experiments/ClimaEarth/hierarchy/climate_plots.jl @@ -0,0 +1,89 @@ +# paper_figs +using NCDatasets +using Statistics +using Plots + +import Interpolations: LinearInterpolation +import DelimitedFiles: writedlm, readdlm + +include("plot_helper.jl") + +for job_id in ["dry_held_suarez", "moist_held_suarez"] + if isinteractive() + DATA_DIR = "experiments/ClimaEarth/$job_id/$job_id/clima_atmos/output_active/" + else + build = ENV["BUILDKITE_BUILD_NUMBER"] + DATA_DIR = "/central/scratch/esm/slurm-buildkite/climacoupler-ci/$build/climacoupler-ci/$job_id/$job_id/clima_atmos/output_active/" + end + + reduction = "6h_inst" + PLOT_DIR = "paper_figs" + + mkpath(PLOT_DIR) + + # SUPPLEMENTAL: animation of surface temperature + ta_sfc, lat, lon, z, time = get_nc_data_all("ta", reduction, DATA_DIR) + anim = Plots.@animate for i in 1:size(ta_sfc, 1) + Plots.contourf( + lon, + lat, + ta_sfc[i, :, :, 1]', + xlabel = "Longitude", + ylabel = "Latitude", + title = "$var", + color = :viridis, + clims = (260, 315), + ) + end + Plots.mp4(anim, joinpath(PLOT_DIR, "anim_ta_sfc.mp4"), fps = 10) + + # Figure 2: climatology + # this plots the time-mean (upper-level and surface) slices and zonal means of + # the mass streamfunction, zonal wind, meridional wind, temperature, max. Eady growth rate, and vertical velocity + vars = ["mass_strf", "va", "ua", "ta", "egr", "wa"] + for var in vars + plot_climate(var, DATA_DIR, PLOT_DIR, job_id, reduction = reduction, interpolate_to_pressure = true) + end + + # Figure 4: storm track diagnostics + # this extracts the eddy heat flux and plots its climatology + lev_st = 6 + ta_zm, ta_sfc, lat, lon, z = mean_climate_data("ta", reduction, DATA_DIR, lev_i = lev_st) + va_zm, va_sfc, lat, lon, z = mean_climate_data("va", reduction, DATA_DIR, lev_i = lev_st) + vT_zm, vT_sfc, lat, lon, z = mean_climate_data("vt", reduction, DATA_DIR, lev_i = lev_st) + heat_flux_zm = vT_zm .- va_zm .* ta_zm + + pa_zm, ~, ~, ~, ~ = mean_climate_data("pfull", reduction, DATA_DIR) + pa_zm = pa_zm ./ 100 # convert to hPa + pa_grid = [950, 800, 700, 600, 500, 400, 300, 200, 50] + + heat_flux_int_zm = interpolate_to_pressure_coord_2d(heat_flux_zm, pa_zm, pa_grid) + Plots.contourf( + lat, + -pa_grid, + heat_flux_int_zm', + xlabel = "Latitude (deg N)", + ylabel = "Pressure (hPa)", + title = "Heat flux", + color = :viridis, + ylims = (-pa_grid[1], -pa_grid[end]), + yticks = (-pa_grid, pa_grid), + ) + png(joinpath(PLOT_DIR, "$(job_id)_heat_flux.png")) + + # Figure 5: storm track diagnostics reduced to timeseries + # this plots the eddy heat flux and max. Eady growth rate in a sectorial selection + lev_i, lat_s_i, lat_n_i, lon_w_i, lon_e_i = lev_st, 60, 75, 1, 30 + println( + "Sectorial selevtion for timeseries: \n level: $(z[lev_i]), lat: $(lat[lat_s_i]) to $(lat[lat_n_i]), lon: $(lon[lon_w_i]) to $(lon[lon_e_i])", + ) + + egr_all, lat, lon, z, time = get_nc_data_all("egr", reduction, DATA_DIR) + egr_t = point_timeseries_data(egr_all, [lon_w_i, lon_e_i], [lat_s_i, lat_n_i], lev_i) + + vT_all, lat, lon, z, time = get_nc_data_all("vt", reduction, DATA_DIR) + va_all, lat, lon, z, time = get_nc_data_all("va", reduction, DATA_DIR) + ta_all, lat, lon, z, time = get_nc_data_all("ta", reduction, DATA_DIR) + heat_flux_all = vT_all .- va_all .* ta_all + heat_flux_t = point_timeseries_data(heat_flux_all, [lon_w_i, lon_e_i], [lat_s_i, lat_n_i], lev_i) +end diff --git a/experiments/ClimaEarth/hierarchy/plot_helper.jl b/experiments/ClimaEarth/hierarchy/plot_helper.jl new file mode 100644 index 0000000000..e326d8904e --- /dev/null +++ b/experiments/ClimaEarth/hierarchy/plot_helper.jl @@ -0,0 +1,119 @@ +# plot_helper + +""" + get_nc_data_all(var, red, DATA_DIR) + +Reads the netcdf file for the variable `var` and reduction `red` from the directory `DATA_DIR` and returns the variable, lat, lon, z, and time. +""" +get_nc_data_all = (var, red, DATA_DIR) -> begin + ds = NCDataset("$DATA_DIR/$(var)_$red.nc") + var = ds["$var"][:, :, :, :] + lat = ds["lat"][:] + lon = ds["lon"][:] + z = ds["z"][:] + time = ds["time"][:] + close(ds) + return var, lat, lon, z, time +end + +""" + mean_climate_data(varname, reduction, DATA_DIR; lev_i = 1, spinup=1) + +Postprocesses the climate data for the variable `varname` and `reduction` from the directory `DATA_DIR`. Returns the zonal mean and horizontal surface slice mean of the variable. +""" +mean_climate_data = + (varname, reduction, DATA_DIR; lev_i = 1, spinup = 1) -> begin + + var, lat, lon, z, time = get_nc_data_all(varname, reduction, DATA_DIR) + @assert spinup < size(var, 1) + + var_time_zonal_mean = mean(var[spinup:end, :, :, :], dims = (1, 2))[1, 1, :, :] + var_time_mean_sfc = mean(var[spinup:end, :, :, :], dims = (1))[1, :, :, lev_i] + + return var_time_zonal_mean, var_time_mean_sfc, lat, lon, z + end + +""" + point_timeseries_data(variable, lon_i, lat_i, lev_i) + +Returns the time series data for the variable `variable` at the indices `lon_i`, `lat_i`, and `lev_i`. +""" +point_timeseries_data = + (variable, lon_i, lat_i, lev_i) -> begin + + variable_time_mean = mean(variable[:, lon_i[1]:lon_i[2], lat_i[1]:lat_i[2], lev_i], dims = (2, 3))[:, 1, 1] + + return variable_time_mean + end + +""" + plot_climate(var, DATA_DIR, PLOT_DIR, job_id; reduction = "inst", interpolate_to_pressure = false) + +Plots the zonal mean and horizontal surface slice mean of the variable `var` from the directory `DATA_DIR` and saves the plots in the directory `PLOT_DIR`. +""" +function plot_climate(var, DATA_DIR, PLOT_DIR, job_id; reduction = "inst", interpolate_to_pressure = false) + strf_zm, strf_sfc, lat, lon, z = mean_climate_data(var, reduction, DATA_DIR) + strf_zm, strf_upper, lat, lon, z = mean_climate_data(var, reduction, DATA_DIR, lev_i = 10) + + # vertical-lat plot of zonal and time mean + if interpolate_to_pressure + pa_zm, ~, ~, ~, ~ = mean_climate_data("pfull", reduction, DATA_DIR) + pa_zm = pa_zm ./ 100 # convert to hPa + pa_grid = [950, 800, 700, 600, 500, 400, 300, 200, 50] + strf_zm = interpolate_to_pressure_coord_2d(strf_zm, pa_zm, pa_grid) + Plots.contourf( + lat, + -pa_grid, + strf_zm', + xlabel = "Latitude (deg N)", + ylabel = "Pressure (hPa)", + title = "$var", + color = :viridis, + ylims = (-pa_grid[1], -pa_grid[end]), + yticks = (-pa_grid, pa_grid), + )# , clims=(-1e10, 1e10)) + png(joinpath(PLOT_DIR, "$(job_id)_$(var)_pa.png")) + else + Plots.contourf( + lat, + z, + strf_zm', + xlabel = "Latitude", + ylabel = "Height (km)", + title = "$var", + color = :viridis, + ylims = (0, 3e4), + yscale = :log10, + yticks = ([1e3, 5e3, 10e3, 20e3, 30e3], ["1", "5", "10", "20", "30"]), + )# , clims=(-1e10, 1e10) + png(joinpath(PLOT_DIR, "$(job_id)_$var.png")) + end + + # horizontal slices + Plots.contourf(lon, lat, strf_sfc', xlabel = "Longitude", ylabel = "Latitude", title = "$var", color = :viridis)#, clims=(-1e10, 1e10)) + png(joinpath(PLOT_DIR, "$(job_id)_$(var)_sfc.png")) + + Plots.contourf(lon, lat, strf_upper', xlabel = "Longitude", ylabel = "Latitude", title = "$var", color = :viridis)#, clims=(-1e10, 1e10)) + png(joinpath(PLOT_DIR, "$(job_id)_$(var)_10km.png")) +end + +""" + interpolate_to_pressure_coord_2d(var_zm, pa, pa_grid) + +Interpolates the 2D variable `var_zm` to the pressure grid `pa_grid` using the pressure values `pa`. +""" +function interpolate_to_pressure_coord_2d(var_zm, pa, pa_grid) + var_on_pa = zeros(size(var_zm, 1), length(pa_grid)) + for lat_i in collect(1:size(var_zm, 1)) + # Extract ua and corresponding ta values + var_values = var_zm[lat_i, :] + pa_values = pa[lat_i, :] + + # Interpolate ua onto ta_grid + for (pa_j, pa_val) in enumerate(pa_grid) + itp_var = LinearInterpolation(-pa_values, var_values) + var_on_pa[lat_i, pa_j] = itp_var(-pa_val) + end + end + return var_on_pa +end