Skip to content

Commit 4754f88

Browse files
committed
Account for ERA5/ClimaAtmos topo differences when initializing pressure. Account for lapse rate in mslp sea level reduction (diagnostics).
1 parent b3696f7 commit 4754f88

File tree

3 files changed

+353
-101
lines changed

3 files changed

+353
-101
lines changed

src/diagnostics/core_diagnostics.jl

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,24 +1647,32 @@ add_diagnostic_variable!(
16471647
function compute_mslp!(out, state, cache, time)
16481648
thermo_params = CAP.thermodynamics_params(cache.params)
16491649
g = TD.Parameters.grav(thermo_params)
1650-
R_m_surf = Fields.level(
1651-
lazy.(TD.gas_constant_air.(thermo_params, cache.precomputed.ᶜts)),
1652-
1,
1653-
)
1650+
ts_level = Fields.level(cache.precomputed.ᶜts, 1)
1651+
R_m_surf = @. lazy(TD.gas_constant_air(thermo_params, ts_level))
16541652

1655-
# get pressure, temperature, and height at the lowest atmospheric level
16561653
p_level = Fields.level(cache.precomputed.ᶜp, 1)
1657-
t_level = Fields.level(
1658-
lazy.(TD.air_temperature.(thermo_params, cache.precomputed.ᶜts)),
1659-
1,
1660-
)
1654+
t_level = @. lazy(TD.air_temperature(thermo_params, ts_level))
16611655
z_level = Fields.level(Fields.coordinate_field(state.c.ρ).z, 1)
16621656

1663-
# compute sea level pressure using the hypsometric equation
1657+
# Reduce to mean sea level using hypsometric formulation with lapse rate adjustment
1658+
# Using constant lapse rate Γ = 6.5 K/km, with virtual temperature
1659+
# represented via R_m_surf. This reduces biases over
1660+
# very cold or very warm high-topography regions.
1661+
FT = Spaces.undertype(Fields.axes(state.c.ρ))
1662+
Γ = FT(6.5e-3) # K m^-1
1663+
1664+
# p_msl = p_z0 * [1 + Γ * z / T_z0]^( g / (R_m Γ))
1665+
# where:
1666+
# - p_z0 pressure at the lowest model level
1667+
# - T_z0 air temperature at the lowest model level
1668+
# - R_m moist-air gas constant at the surface (R_m_surf), which
1669+
# accounts for virtual-temperature effects in the exponent
1670+
# - Γ constant lapse rate (6.5 K/km here)
1671+
16641672
if isnothing(out)
1665-
return @. p_level * exp(g * z_level / (R_m_surf * t_level))
1673+
return p_level .* (1 .+ Γ .* z_level ./ t_level) .^ (g / Γ ./ R_m_surf)
16661674
else
1667-
@. out = p_level * exp(g * z_level / (R_m_surf * t_level))
1675+
out .= p_level .* (1 .+ Γ .* z_level ./ t_level) .^ (g / Γ ./ R_m_surf)
16681676
end
16691677
end
16701678

@@ -1673,7 +1681,7 @@ add_diagnostic_variable!(
16731681
long_name = "Mean Sea Level Pressure",
16741682
standard_name = "mean_sea_level_pressure",
16751683
units = "Pa",
1676-
comments = "Mean sea level pressure computed from the hypsometric equation",
1684+
comments = "Mean sea level pressure computed using a lapse-rate-dependent hypsometric reduction (ERA-style; Γ=6.5 K/km with virtual temperature via moist gas constant).",
16771685
compute! = compute_mslp!,
16781686
)
16791687

src/initial_conditions/initial_conditions.jl

Lines changed: 207 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -386,73 +386,233 @@ function overwrite_initial_conditions!(
386386
)
387387
end
388388

389+
"""
390+
correct_surface_pressure_for_topography!(
391+
p_sfc,
392+
file_path,
393+
face_space,
394+
Y,
395+
ᶜT,
396+
ᶜq_tot,
397+
thermo_params,
398+
regridder_kwargs;
399+
surface_altitude_var = "z_sfc",
400+
)
401+
402+
Adjusts the surface pressure field `p_sfc` to account for mismatches between
403+
ERA5 (file) surface altitude and the model orography when specifying pressure.
404+
405+
Δz = z_model_surface - z_sfc
406+
407+
and applies a hydrostatic correction at the surface using the local moist gas
408+
constant and temperature at the surface:
409+
410+
p_sfc .= p_sfc .* exp.(-Δz * g ./ (R_m_sfc .* T_sfc))
411+
412+
where:
413+
- `g` is gravitational acceleration from `thermo_params`
414+
- `R_m_sfc` is the moist-air gas constant evaluated from `ᶜq_tot` at the surface
415+
- `T_sfc` is the air temperature from `ᶜT` at the surface
416+
417+
Returns `true` if the correction is applied; returns `false` if the surface
418+
altitude field cannot be loaded.
419+
420+
Arguments
421+
- `p_sfc`: face field of surface pressure to be corrected (modified in-place)
422+
- `file_path`: path to the ERA5-derived initialization NetCDF file
423+
- `face_space`: face space of the model grid (for reading/regridding)
424+
- `Y`: prognostic state, used to obtain model surface height
425+
- `ᶜT`: center field of temperature
426+
- `ᶜq_tot`: center field of total specific humidity
427+
- `thermo_params`: thermodynamics parameter set
428+
- `regridder_kwargs`: keyword arguments forwarded to the regridder
429+
- `surface_altitude_var`: variable name for surface altitude (default `"z_sfc"`)
430+
"""
431+
function correct_surface_pressure_for_topography!(
432+
p_sfc,
433+
file_path,
434+
face_space,
435+
Y,
436+
ᶜT,
437+
ᶜq_tot,
438+
thermo_params,
439+
regridder_kwargs;
440+
surface_altitude_var = "z_sfc",
441+
)
442+
regridder_type = :InterpolationsRegridder
443+
ᶠz_surface = Fields.level(
444+
SpaceVaryingInputs.SpaceVaryingInput(
445+
file_path,
446+
surface_altitude_var,
447+
face_space;
448+
regridder_type,
449+
regridder_kwargs = regridder_kwargs,
450+
),
451+
Fields.half,
452+
)
453+
454+
if ᶠz_surface === nothing
455+
return false
456+
end
457+
458+
FT = eltype(thermo_params)
459+
grav = thermo_params.grav
460+
461+
ᶠz_model_surface = Fields.level(Fields.coordinate_field(Y.f).z, Fields.half)
462+
ᶠΔz = zeros(face_space)
463+
@. ᶠΔz = ᶠz_model_surface - ᶠz_surface
464+
465+
ᶠR_m = ᶠinterp.(TD.gas_constant_air.(thermo_params, TD.PhasePartition.(ᶜq_tot)))
466+
ᶠR_m_sfc = Fields.level(ᶠR_m, Fields.half)
467+
468+
ᶠT = ᶠinterp.(ᶜT)
469+
ᶠT_sfc = Fields.level(ᶠT, Fields.half)
470+
471+
@. p_sfc = p_sfc * exp(FT(-1) * ᶠΔz * grav / (ᶠR_m_sfc * ᶠT_sfc))
472+
473+
@info "Adjusted surface pressure to account for ERA5/model surface-height differences."
474+
return true
475+
end
476+
389477
# WeatherModel function using the shared implementation
478+
"""
479+
overwrite_initial_conditions!(initial_condition::WeatherModel, Y, thermo_params; use_full_pressure=false)
480+
481+
Overwrite the prognostic state `Y` with ERA5-derived initial conditions on the model grid.
482+
483+
484+
- Derives the model's target vertical levels from `Y.c` (on CPU).
485+
- Obtains the ERA5-derived IC NetCDF path via `weather_model_data_path` (with
486+
any caller-provided kwargs forwarded there), then constructs `SpaceVaryingInput`
487+
fields to regrid ERA5 variables onto the model's center and face spaces.
488+
- Populates the thermodynamic state, density, velocity components, total energy,
489+
and moisture. If EDMF subdomains exist, initializes those as well.
490+
491+
Pressure initialization (`use_full_pressure`):
492+
- If `use_full_pressure == true` and the IC file contains a 3D pressure field
493+
`p_3d(lon,lat,z)`, then pressure is taken directly from `p_3d` and regridded.
494+
- Otherwise, pressure is obtained by hydrostatic integration starting from the
495+
surface pressure `p(lon,lat)` (broadcast in `z` in the IC file), using the
496+
regridded temperature `t` and specific humidity `q`. If the dataset provides
497+
surface altitude `z_sfc` (derived from ERA5 surface geopotential), the surface
498+
pressure is first corrected for model-versus-ERA5 topographic differences
499+
in order to adjust the reported ERA5 surface pressure.
500+
501+
Expected variables in the IC file:
502+
- 3D: `u`, `v`, `w`, `t`, `q` (and optionally `p_3d`, cloud water variables)
503+
- 2D broadcast in `z`: `p` (surface pressure), `skt` (skin temperature), and
504+
optionally `z_sfc` (surface altitude)
505+
506+
Notes:
507+
- When generating 3D ICs (via `to_z_levels(...; interp3d=true)` in
508+
`weather_model_data_path`), the file can include `p_3d` and a `z_physical`
509+
field on the target grid, enabling the full-pressure path described above.
510+
"""
390511
function overwrite_initial_conditions!(
391512
initial_condition::WeatherModel,
392513
Y,
393-
thermo_params,
514+
thermo_params;
515+
use_full_pressure::Bool = false,
394516
)
517+
regridder_type = :InterpolationsRegridder
518+
interpolation_method = Intp.Linear()
395519
extrapolation_bc = (Intp.Periodic(), Intp.Flat(), Intp.Flat())
396520

397-
# Extract face coordinates and compute center midpoints
398-
# Compute target levels on CPU to avoid GPU reductions
399-
z_arr_cpu = Array(Fields.field2array(Fields.coordinate_field(Y.c).z))
400-
icol = argmin(z_arr_cpu[1, :])
401-
target_levels = z_arr_cpu[:, icol]
521+
# target_levels defines vertical z for 1D interp,
522+
# if upstream processed ERA5 init file is missing, it is generated with to_z_levels_1d.
523+
z_arr = Array(Fields.field2array(Fields.coordinate_field(Y.c).z))
524+
z_top = round(maximum(z_arr))
525+
target_levels = collect(0.0:300.0:z_top)
526+
527+
@info "Calling weather_model_data_path" (
528+
start_date = initial_condition.start_date,
529+
era5_dir = initial_condition.era5_initial_condition_dir,
530+
)
402531

403532
file_path = weather_model_data_path(
404533
initial_condition.start_date,
405534
target_levels,
406-
initial_condition.era5_initial_condition_dir,
535+
initial_condition.era5_initial_condition_dir;
407536
)
408537

409-
regridder_kwargs = (; extrapolation_bc)
538+
regridder_kwargs = (; extrapolation_bc, interpolation_method)
539+
410540
isfile(file_path) || error("$(file_path) is not a file")
411541
@info "Overwriting initial conditions with data from file $(file_path)"
412542

413543
center_space = Fields.axes(Y.c)
414544
face_space = Fields.axes(Y.f)
415545

416-
# Using surface pressure, air temperature and specific humidity
417-
# from the dataset, compute air pressure.
418-
p_sfc = Fields.level(
419-
SpaceVaryingInputs.SpaceVaryingInput(
420-
file_path,
421-
"p",
422-
face_space,
423-
regridder_kwargs = regridder_kwargs,
424-
),
425-
Fields.half,
426-
)
427546
ᶜT = SpaceVaryingInputs.SpaceVaryingInput(
428547
file_path,
429548
"t",
430-
center_space,
549+
center_space;
550+
regridder_type,
431551
regridder_kwargs = regridder_kwargs,
432552
)
433553
ᶜq_tot = SpaceVaryingInputs.SpaceVaryingInput(
434554
file_path,
435555
"q",
436-
center_space,
556+
center_space;
557+
regridder_type,
437558
regridder_kwargs = regridder_kwargs,
438559
)
439560

440-
# With the known temperature (ᶜT) and moisture (ᶜq_tot) profile,
441-
# recompute the pressure levels assuming hydrostatic balance is maintained.
442-
# Uses the ClimaCore `column_integral_indefinite!` function to solve
443-
# ∂(ln𝑝)/∂z = -g/(Rₘ(q)T), where
444-
# p is the local pressure
445-
# g is the gravitational constant
446-
# q is the specific humidity
447-
# Rₘ is the gas constant for moist air
448-
# T is the air temperature
449-
# p is then updated with the integral result, given p_sfc,
450-
# following which the thermodynamic state is constructed.
451-
ᶜ∂lnp∂z = @. -thermo_params.grav /
452-
(TD.gas_constant_air(thermo_params, TD.PhasePartition(ᶜq_tot)) * ᶜT)
453-
ᶠlnp_over_psfc = zeros(face_space)
454-
Operators.column_integral_indefinite!(ᶠlnp_over_psfc, ᶜ∂lnp∂z)
455-
ᶠp = p_sfc .* exp.(ᶠlnp_over_psfc)
561+
use_p3d = use_full_pressure && NC.NCDataset(file_path) do ds
562+
haskey(ds, "p_3d")
563+
end
564+
ᶠp = if use_p3d
565+
SpaceVaryingInputs.SpaceVaryingInput(
566+
file_path,
567+
"p_3d",
568+
face_space;
569+
regridder_type,
570+
regridder_kwargs = regridder_kwargs,
571+
)
572+
else
573+
if use_full_pressure
574+
@warn "Requested full pressure initialization, but variable `p_3d` is missing in $(file_path). Falling back to hydrostatic integration from surface pressure."
575+
end
576+
# Using surface pressure, air temperature and specific humidity
577+
# from the dataset, compute air pressure by hydrostatic integration.
578+
p_sfc = Fields.level(
579+
SpaceVaryingInputs.SpaceVaryingInput(
580+
file_path,
581+
"p",
582+
face_space;
583+
regridder_type,
584+
regridder_kwargs = regridder_kwargs,
585+
),
586+
Fields.half,
587+
)
588+
# Apply hydrostatic surface-pressure correction only if surface altitude is available
589+
surface_altitude_var = "z_sfc"
590+
has_surface_altitude = NC.NCDataset(file_path) do ds
591+
haskey(ds, surface_altitude_var)
592+
end
593+
if has_surface_altitude
594+
correct_surface_pressure_for_topography!(
595+
p_sfc,
596+
file_path,
597+
face_space,
598+
Y,
599+
ᶜT,
600+
ᶜq_tot,
601+
thermo_params,
602+
regridder_kwargs;
603+
surface_altitude_var = surface_altitude_var,
604+
)
605+
else
606+
@warn "Skipping topographic correction because variable `$surface_altitude_var` is missing from $(file_path)."
607+
end
608+
# With the known temperature (ᶜT) and moisture (ᶜq_tot) profile,
609+
# recompute the pressure levels assuming hydrostatic balance is maintained.
610+
ᶜ∂lnp∂z = @. -thermo_params.grav /
611+
(TD.gas_constant_air(thermo_params, TD.PhasePartition(ᶜq_tot)) * ᶜT)
612+
ᶠlnp_over_psfc = zeros(face_space)
613+
Operators.column_integral_indefinite!(ᶠlnp_over_psfc, ᶜ∂lnp∂z)
614+
p_sfc .* exp.(ᶠlnp_over_psfc)
615+
end
456616
ᶜts = TD.PhaseEquil_pTq.(thermo_params, ᶜinterp.(ᶠp), ᶜT, ᶜq_tot)
457617

458618
# Assign prognostic variables from equilibrium moisture models
@@ -464,19 +624,22 @@ function overwrite_initial_conditions!(
464624
SpaceVaryingInputs.SpaceVaryingInput(
465625
file_path,
466626
"u",
467-
center_space,
627+
center_space;
628+
regridder_type,
468629
regridder_kwargs = regridder_kwargs,
469630
),
470631
SpaceVaryingInputs.SpaceVaryingInput(
471632
file_path,
472633
"v",
473-
center_space,
634+
center_space;
635+
regridder_type,
474636
regridder_kwargs = regridder_kwargs,
475637
),
476638
SpaceVaryingInputs.SpaceVaryingInput(
477639
file_path,
478640
"w",
479-
center_space,
641+
center_space;
642+
regridder_type,
480643
regridder_kwargs = regridder_kwargs,
481644
),
482645
)
@@ -508,14 +671,16 @@ function overwrite_initial_conditions!(
508671
SpaceVaryingInputs.SpaceVaryingInput(
509672
file_path,
510673
"cswc",
511-
center_space,
674+
center_space;
675+
regridder_type,
512676
regridder_kwargs = regridder_kwargs,
513677
) .* Y.c.ρ
514678
Y.c.ρq_rai .=
515679
SpaceVaryingInputs.SpaceVaryingInput(
516680
file_path,
517681
"crwc",
518-
center_space,
682+
center_space;
683+
regridder_type,
519684
regridder_kwargs = regridder_kwargs,
520685
) .* Y.c.ρ
521686
end

0 commit comments

Comments
 (0)