|
| 1 | +# # Cloudless Aquaplanet |
| 2 | + |
| 3 | +redirect_stderr(IOContext(stderr, :stacktrace_types_limited => Ref(false))) |
| 4 | + |
| 5 | +#= |
| 6 | +## Configuration initialization |
| 7 | +=# |
| 8 | + |
| 9 | +#= |
| 10 | +### Package Import |
| 11 | +=# |
| 12 | + |
| 13 | +## standard packages |
| 14 | +import Dates |
| 15 | +import YAML |
| 16 | + |
| 17 | +# ## ClimaESM packages |
| 18 | +import ClimaAtmos as CA |
| 19 | +import ClimaComms |
| 20 | +import ClimaCore as CC |
| 21 | + |
| 22 | +# ## Coupler specific imports |
| 23 | +import ClimaCoupler |
| 24 | +import ClimaCoupler: |
| 25 | + BCReader, |
| 26 | + ConservationChecker, |
| 27 | + Checkpointer, |
| 28 | + Diagnostics, |
| 29 | + FieldExchanger, |
| 30 | + FluxCalculator, |
| 31 | + Interfacer, |
| 32 | + Regridder, |
| 33 | + TimeManager, |
| 34 | + Utilities |
| 35 | + |
| 36 | +pkg_dir = pkgdir(ClimaCoupler) |
| 37 | + |
| 38 | +#= |
| 39 | +### Helper Functions |
| 40 | +These will be eventually moved to their respective component model and diagnostics packages, and so they should not |
| 41 | +contain any internals of the ClimaCoupler source code, except extensions to the Interfacer functions. |
| 42 | +=# |
| 43 | + |
| 44 | +## helpers for component models |
| 45 | +include("components/atmosphere/climaatmos.jl") |
| 46 | +include("components/ocean/slab_ocean.jl") |
| 47 | + |
| 48 | +## helpers for user-specified IO |
| 49 | +include("user_io/user_diagnostics.jl") |
| 50 | +include("user_io/user_logging.jl") |
| 51 | + |
| 52 | +include("user_io/io_helpers.jl") |
| 53 | + |
| 54 | +#= |
| 55 | +### Setup simulation parameters |
| 56 | +Here we follow ClimaCore's dry Held-Suarez `held_suarez_rhoe` example. |
| 57 | +=# |
| 58 | + |
| 59 | +## run names |
| 60 | +run_name = "cloudless_aquaplanet" |
| 61 | +coupler_output_dir = "$run_name" |
| 62 | +const FT = Float64 |
| 63 | +restart_dir = "unspecified" |
| 64 | +restart_t = Int(0) |
| 65 | + |
| 66 | +## coupler simulation specific configuration |
| 67 | +Δt_cpl = Float64(400) |
| 68 | +t_end = "1000days" |
| 69 | +tspan = (Float64(0.0), Float64(time_to_seconds(t_end))) |
| 70 | +start_date = "19790301" |
| 71 | +hourly_checkpoint = true |
| 72 | + |
| 73 | +## namelist |
| 74 | +config_dict = Dict( |
| 75 | + # general |
| 76 | + "FLOAT_TYPE" => string(FT), |
| 77 | + # file paths |
| 78 | + "atmos_config_file" => nothing, |
| 79 | + "coupler_toml_file" => nothing, |
| 80 | + "coupler_output_dir" => coupler_output_dir, |
| 81 | + "mode_name" => "", |
| 82 | + "run_name" => run_name, |
| 83 | + "atmos_config_repo" => "ClimaAtmos", |
| 84 | + # timestepping |
| 85 | + "dt" => "$(Δt_cpl)secs", |
| 86 | + "dt_save_to_sol" => "1days", |
| 87 | + "t_end" => t_end, |
| 88 | + "start_date" => "19790301", |
| 89 | + # domain |
| 90 | + "h_elem" => 4, |
| 91 | + "z_elem" => 10, |
| 92 | + "z_max" => 30000.0, # semi-high top |
| 93 | + "dz_bottom" => 300.0, |
| 94 | + "nh_poly" => 4, |
| 95 | + # output |
| 96 | + "dt_save_to_sol" => "1days", |
| 97 | + # numerics |
| 98 | + "apply_limiter" => false, |
| 99 | + "viscous_sponge" => false, |
| 100 | + "rayleigh_sponge" => false, |
| 101 | + "vert_diff" => "true", |
| 102 | + "hyperdiff" => "ClimaHyperdiffusion", |
| 103 | + # run |
| 104 | + "job_id" => run_name, |
| 105 | + "surface_setup" => "PrescribedSurface", |
| 106 | + # diagnostic (nested with period and short_name) |
| 107 | + "output_default_diagnostics" => false, |
| 108 | + "diagnostics" => [ |
| 109 | + Dict( |
| 110 | + "short_name" => [ |
| 111 | + "mse", |
| 112 | + "lr", |
| 113 | + "mass_strf", |
| 114 | + "stab", |
| 115 | + "vt", |
| 116 | + "egr", |
| 117 | + "ua", |
| 118 | + "va", |
| 119 | + "wa", |
| 120 | + "ta", |
| 121 | + "rhoa", |
| 122 | + "pfull", |
| 123 | + ], |
| 124 | + "period" => "6hours", |
| 125 | + "reduction" => "inst", |
| 126 | + ), |
| 127 | + ], |
| 128 | + # held-suarez specific |
| 129 | + "precip_model" => "0M", |
| 130 | + "moist" => "equil", |
| 131 | + "prognostic_surface" => "PrescribedSurfaceTemperature", |
| 132 | + "turb_flux_partition" => "CombinedStateFluxes", |
| 133 | + "rad" => "gray", |
| 134 | +) |
| 135 | +# TODO: increase diffusion coef? |
| 136 | + |
| 137 | +## merge dictionaries of command line arguments, coupler dictionary and component model dictionaries |
| 138 | +atmos_config_dict, config_dict = get_atmos_config_dict(config_dict) |
| 139 | +atmos_config_object = CA.AtmosConfig(atmos_config_dict) |
| 140 | + |
| 141 | +#= |
| 142 | +## Setup Communication Context |
| 143 | +=# |
| 144 | + |
| 145 | +comms_ctx = Utilities.get_comms_context(Dict("device" => "auto")) |
| 146 | +ClimaComms.init(comms_ctx) |
| 147 | + |
| 148 | +#= |
| 149 | +### I/O Directory Setup |
| 150 | +=# |
| 151 | + |
| 152 | +dir_paths = setup_output_dirs(output_dir = coupler_output_dir, comms_ctx = comms_ctx) |
| 153 | +ClimaComms.iamroot(comms_ctx) ? @info(config_dict) : nothing |
| 154 | + |
| 155 | +#= |
| 156 | +## Component Model Initialization |
| 157 | +=# |
| 158 | + |
| 159 | +#= |
| 160 | +### Atmosphere |
| 161 | +This uses the `ClimaAtmos.jl` model, with parameterization options specified in the `config_dict_atmos` dictionary. |
| 162 | +=# |
| 163 | + |
| 164 | +## init atmos model component |
| 165 | +atmos_sim = atmos_init(atmos_config_object); |
| 166 | +thermo_params = get_thermo_params(atmos_sim) |
| 167 | + |
| 168 | +#= |
| 169 | +### Boundary Space |
| 170 | +=# |
| 171 | + |
| 172 | +## init a 2D boundary space at the surface |
| 173 | +boundary_space = CC.Spaces.horizontal_space(atmos_sim.domain.face_space) # TODO: specify this in the coupler and pass it to all component models #665 |
| 174 | + |
| 175 | +#= |
| 176 | +### Surface Model: Slab Ocean |
| 177 | +=# |
| 178 | +ocean_sim = ocean_init( |
| 179 | + FT; |
| 180 | + tspan = tspan, |
| 181 | + dt = Δt_cpl, |
| 182 | + space = boundary_space, |
| 183 | + saveat = saveat, |
| 184 | + area_fraction = ones(boundary_space), |
| 185 | + thermo_params = thermo_params, |
| 186 | + evolving = evolving_ocean, |
| 187 | +) |
| 188 | + |
| 189 | +#= |
| 190 | +## Coupler Initialization |
| 191 | +=# |
| 192 | + |
| 193 | +## coupler exchange fields |
| 194 | +coupler_field_names = ( |
| 195 | + :T_S, |
| 196 | + :z0m_S, |
| 197 | + :z0b_S, |
| 198 | + :ρ_sfc, |
| 199 | + :q_sfc, |
| 200 | + :surface_direct_albedo, |
| 201 | + :surface_diffuse_albedo, |
| 202 | + :beta, |
| 203 | + :F_turb_energy, |
| 204 | + :F_turb_moisture, |
| 205 | + :F_turb_ρτxz, |
| 206 | + :F_turb_ρτyz, |
| 207 | + :F_radiative, |
| 208 | + :P_liq, |
| 209 | + :P_snow, |
| 210 | + :radiative_energy_flux_toa, |
| 211 | + :P_net, |
| 212 | + :temp1, |
| 213 | + :temp2, |
| 214 | +) |
| 215 | +coupler_fields = |
| 216 | + NamedTuple{coupler_field_names}(ntuple(i -> CC.Fields.zeros(boundary_space), length(coupler_field_names))) |
| 217 | +Utilities.show_memory_usage(comms_ctx) |
| 218 | + |
| 219 | +## model simulations |
| 220 | +model_sims = (atmos_sim = atmos_sim, ocean_sim = ocean_sim); |
| 221 | + |
| 222 | +## dates |
| 223 | +dates = (; date = [date], date0 = [date0], date1 = [Dates.firstdayofmonth(date0)], new_month = [false]) |
| 224 | + |
| 225 | +#= |
| 226 | +## Initialize Callbacks |
| 227 | +=# |
| 228 | +checkpoint_cb = TimeManager.HourlyCallback( |
| 229 | + dt = hourly_checkpoint_dt, |
| 230 | + func = checkpoint_sims, |
| 231 | + ref_date = [dates.date[1]], |
| 232 | + active = hourly_checkpoint, |
| 233 | +) # 20 days |
| 234 | +update_firstdayofmonth!_cb = TimeManager.MonthlyCallback( |
| 235 | + dt = FT(1), |
| 236 | + func = TimeManager.update_firstdayofmonth!, |
| 237 | + ref_date = [dates.date1[1]], |
| 238 | + active = true, |
| 239 | +) |
| 240 | +dt_water_albedo = parse(FT, filter(x -> !occursin(x, "hours"), dt_rad)) |
| 241 | +albedo_cb = TimeManager.HourlyCallback( |
| 242 | + dt = dt_water_albedo, |
| 243 | + func = FluxCalculator.water_albedo_from_atmosphere!, |
| 244 | + ref_date = [dates.date[1]], |
| 245 | + active = mode_name == "amip", |
| 246 | +) |
| 247 | +callbacks = |
| 248 | + (; checkpoint = checkpoint_cb, update_firstdayofmonth! = update_firstdayofmonth!_cb, water_albedo = albedo_cb) |
| 249 | + |
| 250 | +#= |
| 251 | +## Initialize turbulent fluxes |
| 252 | +
|
| 253 | +Decide on the type of turbulent flux partition (see `FluxCalculator` documentation for more details). |
| 254 | +=# |
| 255 | +turbulent_fluxes = nothing |
| 256 | +if config_dict["turb_flux_partition"] == "CombinedStateFluxes" |
| 257 | + turbulent_fluxes = FluxCalculator.CombinedStateFluxes() |
| 258 | +else |
| 259 | + error("turb_flux_partition must be either PartitionedStateFluxes or CombinedStateFluxes") |
| 260 | +end |
| 261 | + |
| 262 | +#= |
| 263 | +## Initialize Coupled Simulation |
| 264 | +=# |
| 265 | + |
| 266 | +cs = Interfacer.CoupledSimulation{FT}( |
| 267 | + comms_ctx, |
| 268 | + dates, |
| 269 | + boundary_space, |
| 270 | + coupler_fields, |
| 271 | + config_dict, |
| 272 | + conservation_checks, |
| 273 | + [tspan[1], tspan[2]], |
| 274 | + atmos_sim.integrator.t, |
| 275 | + Δt_cpl, |
| 276 | + (; land = land_area_fraction, ocean = zeros(boundary_space), ice = zeros(boundary_space)), |
| 277 | + model_sims, |
| 278 | + mode_specifics, |
| 279 | + diagnostics, |
| 280 | + callbacks, |
| 281 | + dir_paths, |
| 282 | + turbulent_fluxes, |
| 283 | + thermo_params, |
| 284 | +); |
| 285 | + |
| 286 | +#= |
| 287 | +## Restart component model states if specified in the config_dict |
| 288 | +=# |
| 289 | + |
| 290 | +if restart_dir !== "unspecified" |
| 291 | + for sim in cs.model_sims |
| 292 | + if Checkpointer.get_model_prog_state(sim) !== nothing |
| 293 | + Checkpointer.restart_model_state!(sim, comms_ctx, restart_t; input_dir = restart_dir) |
| 294 | + end |
| 295 | + end |
| 296 | +end |
| 297 | + |
| 298 | +#= |
| 299 | +## Initialize Component Model Exchange |
| 300 | +=# |
| 301 | + |
| 302 | +# 1.coupler updates surface model area fractions |
| 303 | +Regridder.update_surface_fractions!(cs) |
| 304 | + |
| 305 | +# 2.surface density (`ρ_sfc`): calculated by the coupler by adiabatically extrapolating atmospheric thermal state to the surface. |
| 306 | +# For this, we need to import surface and atmospheric fields. The model sims are then updated with the new surface density. |
| 307 | +FieldExchanger.import_combined_surface_fields!(cs.fields, cs.model_sims, cs.turbulent_fluxes) |
| 308 | +FieldExchanger.import_atmos_fields!(cs.fields, cs.model_sims, cs.boundary_space, cs.turbulent_fluxes) |
| 309 | +FieldExchanger.update_model_sims!(cs.model_sims, cs.fields, cs.turbulent_fluxes) |
| 310 | + |
| 311 | +# 3.surface vapor specific humidity (`q_sfc`): step surface models with the new surface density to calculate their respective `q_sfc` internally |
| 312 | +Interfacer.step!(ocean_sim, Δt_cpl) |
| 313 | + |
| 314 | +# 4.turbulent fluxes |
| 315 | +## import the new surface properties into the coupler (note the atmos state was also imported in step 3.) |
| 316 | +FieldExchanger.import_combined_surface_fields!(cs.fields, cs.model_sims, cs.turbulent_fluxes) # i.e. T_sfc, albedo, z0, beta, q_sfc |
| 317 | +## calculate turbulent fluxes inside the atmos cache based on the combined surface state in each grid box |
| 318 | +FluxCalculator.combined_turbulent_fluxes!(cs.model_sims, cs.fields, cs.turbulent_fluxes) # this updates the atmos thermo state, sfc_ts |
| 319 | + |
| 320 | +# 5.reinitialize models + radiative flux: prognostic states and time are set to their initial conditions. For atmos, this also triggers the callbacks and sets a nonzero radiation flux (given the new sfc_conditions) |
| 321 | +FieldExchanger.reinit_model_sims!(cs.model_sims) |
| 322 | + |
| 323 | +# 6.update all fluxes: coupler re-imports updated atmos fluxes |
| 324 | +FieldExchanger.import_atmos_fields!(cs.fields, cs.model_sims, cs.boundary_space, cs.turbulent_fluxes) |
| 325 | +FieldExchanger.update_model_sims!(cs.model_sims, cs.fields, cs.turbulent_fluxes) |
| 326 | + |
| 327 | +#= |
| 328 | +## Coupling Loop |
| 329 | +=# |
| 330 | + |
| 331 | +function solve_coupler!(cs) |
| 332 | + (; Δt_cpl, tspan, comms_ctx) = cs |
| 333 | + |
| 334 | + ClimaComms.iamroot(comms_ctx) && @info("Starting coupling loop") |
| 335 | + ## step in time |
| 336 | + for t in ((tspan[begin] + Δt_cpl):Δt_cpl:tspan[end]) |
| 337 | + |
| 338 | + cs.dates.date[1] = TimeManager.current_date(cs, t) |
| 339 | + |
| 340 | + ## print date on the first of month |
| 341 | + if cs.dates.date[1] >= cs.dates.date1[1] |
| 342 | + ClimaComms.iamroot(comms_ctx) && @show(cs.dates.date[1]) |
| 343 | + end |
| 344 | + |
| 345 | + ClimaComms.barrier(comms_ctx) |
| 346 | + |
| 347 | + ## update water albedo from wind at dt_water_albedo (this will be extended to a radiation callback from the coupler) |
| 348 | + TimeManager.trigger_callback!(cs, cs.callbacks.water_albedo) |
| 349 | + |
| 350 | + ## run component models sequentially for one coupling timestep (Δt_cpl) |
| 351 | + FieldExchanger.update_model_sims!(cs.model_sims, cs.fields, cs.turbulent_fluxes) |
| 352 | + |
| 353 | + ## step sims |
| 354 | + FieldExchanger.step_model_sims!(cs.model_sims, t) |
| 355 | + |
| 356 | + ## exchange combined fields and (if specified) calculate fluxes using combined states |
| 357 | + FieldExchanger.import_combined_surface_fields!(cs.fields, cs.model_sims, cs.turbulent_fluxes) # i.e. T_sfc, surface_albedo, z0, beta |
| 358 | + FluxCalculator.combined_turbulent_fluxes!(cs.model_sims, cs.fields, cs.turbulent_fluxes) |
| 359 | + |
| 360 | + FieldExchanger.import_atmos_fields!(cs.fields, cs.model_sims, cs.boundary_space, cs.turbulent_fluxes) # radiative and/or turbulent |
| 361 | + |
| 362 | + ## callback to update the fist day of month if needed (for BCReader) |
| 363 | + TimeManager.trigger_callback!(cs, cs.callbacks.update_firstdayofmonth!) |
| 364 | + |
| 365 | + ## callback to checkpoint model state |
| 366 | + TimeManager.trigger_callback!(cs, cs.callbacks.checkpoint) |
| 367 | + |
| 368 | + end |
| 369 | + |
| 370 | + return nothing |
| 371 | +end |
| 372 | + |
| 373 | + |
| 374 | +solve_coupler!(cs) |
0 commit comments