Skip to content

Commit be24457

Browse files
authored
Merge pull request #4098 from CliMA/cc/improve_wxmodel_pressure_init_diag
Improve Initialization from ERA5 and MSLP Diagnostic
2 parents 2402427 + ec99268 commit be24457

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
@@ -447,73 +447,233 @@ function overwrite_initial_conditions!(
447447
)
448448
end
449449

450+
"""
451+
correct_surface_pressure_for_topography!(
452+
p_sfc,
453+
file_path,
454+
face_space,
455+
Y,
456+
ᶜT,
457+
ᶜq_tot,
458+
thermo_params,
459+
regridder_kwargs;
460+
surface_altitude_var = "z_sfc",
461+
)
462+
463+
Adjusts the surface pressure field `p_sfc` to account for mismatches between
464+
ERA5 (file) surface altitude and the model orography when specifying pressure.
465+
466+
Δz = z_model_surface - z_sfc
467+
468+
and applies a hydrostatic correction at the surface using the local moist gas
469+
constant and temperature at the surface:
470+
471+
p_sfc .= p_sfc .* exp.(-Δz * g ./ (R_m_sfc .* T_sfc))
472+
473+
where:
474+
- `g` is gravitational acceleration from `thermo_params`
475+
- `R_m_sfc` is the moist-air gas constant evaluated from `ᶜq_tot` at the surface
476+
- `T_sfc` is the air temperature from `ᶜT` at the surface
477+
478+
Returns `true` if the correction is applied; returns `false` if the surface
479+
altitude field cannot be loaded.
480+
481+
Arguments
482+
- `p_sfc`: face field of surface pressure to be corrected (modified in-place)
483+
- `file_path`: path to the ERA5-derived initialization NetCDF file
484+
- `face_space`: face space of the model grid (for reading/regridding)
485+
- `Y`: prognostic state, used to obtain model surface height
486+
- `ᶜT`: center field of temperature
487+
- `ᶜq_tot`: center field of total specific humidity
488+
- `thermo_params`: thermodynamics parameter set
489+
- `regridder_kwargs`: keyword arguments forwarded to the regridder
490+
- `surface_altitude_var`: variable name for surface altitude (default `"z_sfc"`)
491+
"""
492+
function correct_surface_pressure_for_topography!(
493+
p_sfc,
494+
file_path,
495+
face_space,
496+
Y,
497+
ᶜT,
498+
ᶜq_tot,
499+
thermo_params,
500+
regridder_kwargs;
501+
surface_altitude_var = "z_sfc",
502+
)
503+
regridder_type = :InterpolationsRegridder
504+
ᶠz_surface = Fields.level(
505+
SpaceVaryingInputs.SpaceVaryingInput(
506+
file_path,
507+
surface_altitude_var,
508+
face_space;
509+
regridder_type,
510+
regridder_kwargs = regridder_kwargs,
511+
),
512+
Fields.half,
513+
)
514+
515+
if ᶠz_surface === nothing
516+
return false
517+
end
518+
519+
FT = eltype(thermo_params)
520+
grav = thermo_params.grav
521+
522+
ᶠz_model_surface = Fields.level(Fields.coordinate_field(Y.f).z, Fields.half)
523+
ᶠΔz = zeros(face_space)
524+
@. ᶠΔz = ᶠz_model_surface - ᶠz_surface
525+
526+
ᶠR_m = ᶠinterp.(TD.gas_constant_air.(thermo_params, TD.PhasePartition.(ᶜq_tot)))
527+
ᶠR_m_sfc = Fields.level(ᶠR_m, Fields.half)
528+
529+
ᶠT = ᶠinterp.(ᶜT)
530+
ᶠT_sfc = Fields.level(ᶠT, Fields.half)
531+
532+
@. p_sfc = p_sfc * exp(FT(-1) * ᶠΔz * grav / (ᶠR_m_sfc * ᶠT_sfc))
533+
534+
@info "Adjusted surface pressure to account for ERA5/model surface-height differences."
535+
return true
536+
end
537+
450538
# WeatherModel function using the shared implementation
539+
"""
540+
overwrite_initial_conditions!(initial_condition::WeatherModel, Y, thermo_params; use_full_pressure=false)
541+
542+
Overwrite the prognostic state `Y` with ERA5-derived initial conditions on the model grid.
543+
544+
545+
- Derives the model's target vertical levels from `Y.c` (on CPU).
546+
- Obtains the ERA5-derived IC NetCDF path via `weather_model_data_path` (with
547+
any caller-provided kwargs forwarded there), then constructs `SpaceVaryingInput`
548+
fields to regrid ERA5 variables onto the model's center and face spaces.
549+
- Populates the thermodynamic state, density, velocity components, total energy,
550+
and moisture. If EDMF subdomains exist, initializes those as well.
551+
552+
Pressure initialization (`use_full_pressure`):
553+
- If `use_full_pressure == true` and the IC file contains a 3D pressure field
554+
`p_3d(lon,lat,z)`, then pressure is taken directly from `p_3d` and regridded.
555+
- Otherwise, pressure is obtained by hydrostatic integration starting from the
556+
surface pressure `p(lon,lat)` (broadcast in `z` in the IC file), using the
557+
regridded temperature `t` and specific humidity `q`. If the dataset provides
558+
surface altitude `z_sfc` (derived from ERA5 surface geopotential), the surface
559+
pressure is first corrected for model-versus-ERA5 topographic differences
560+
in order to adjust the reported ERA5 surface pressure.
561+
562+
Expected variables in the IC file:
563+
- 3D: `u`, `v`, `w`, `t`, `q` (and optionally `p_3d`, cloud water variables)
564+
- 2D broadcast in `z`: `p` (surface pressure), `skt` (skin temperature), and
565+
optionally `z_sfc` (surface altitude)
566+
567+
Notes:
568+
- When generating 3D ICs (via `to_z_levels(...; interp3d=true)` in
569+
`weather_model_data_path`), the file can include `p_3d` and a `z_physical`
570+
field on the target grid, enabling the full-pressure path described above.
571+
"""
451572
function overwrite_initial_conditions!(
452573
initial_condition::WeatherModel,
453574
Y,
454-
thermo_params,
575+
thermo_params;
576+
use_full_pressure::Bool = false,
455577
)
578+
regridder_type = :InterpolationsRegridder
579+
interpolation_method = Intp.Linear()
456580
extrapolation_bc = (Intp.Periodic(), Intp.Flat(), Intp.Flat())
457581

458-
# Extract face coordinates and compute center midpoints
459-
# Compute target levels on CPU to avoid GPU reductions
460-
z_arr_cpu = Array(Fields.field2array(Fields.coordinate_field(Y.c).z))
461-
icol = argmin(z_arr_cpu[1, :])
462-
target_levels = z_arr_cpu[:, icol]
582+
# target_levels defines vertical z for 1D interp,
583+
# if upstream processed ERA5 init file is missing, it is generated with to_z_levels_1d.
584+
z_arr = Array(Fields.field2array(Fields.coordinate_field(Y.c).z))
585+
z_top = round(maximum(z_arr))
586+
target_levels = collect(0.0:300.0:z_top)
587+
588+
@info "Calling weather_model_data_path" (
589+
start_date = initial_condition.start_date,
590+
era5_dir = initial_condition.era5_initial_condition_dir,
591+
)
463592

464593
file_path = weather_model_data_path(
465594
initial_condition.start_date,
466595
target_levels,
467-
initial_condition.era5_initial_condition_dir,
596+
initial_condition.era5_initial_condition_dir;
468597
)
469598

470-
regridder_kwargs = (; extrapolation_bc)
599+
regridder_kwargs = (; extrapolation_bc, interpolation_method)
600+
471601
isfile(file_path) || error("$(file_path) is not a file")
472602
@info "Overwriting initial conditions with data from file $(file_path)"
473603

474604
center_space = Fields.axes(Y.c)
475605
face_space = Fields.axes(Y.f)
476606

477-
# Using surface pressure, air temperature and specific humidity
478-
# from the dataset, compute air pressure.
479-
p_sfc = Fields.level(
480-
SpaceVaryingInputs.SpaceVaryingInput(
481-
file_path,
482-
"p",
483-
face_space,
484-
regridder_kwargs = regridder_kwargs,
485-
),
486-
Fields.half,
487-
)
488607
ᶜT = SpaceVaryingInputs.SpaceVaryingInput(
489608
file_path,
490609
"t",
491-
center_space,
610+
center_space;
611+
regridder_type,
492612
regridder_kwargs = regridder_kwargs,
493613
)
494614
ᶜq_tot = SpaceVaryingInputs.SpaceVaryingInput(
495615
file_path,
496616
"q",
497-
center_space,
617+
center_space;
618+
regridder_type,
498619
regridder_kwargs = regridder_kwargs,
499620
)
500621

501-
# With the known temperature (ᶜT) and moisture (ᶜq_tot) profile,
502-
# recompute the pressure levels assuming hydrostatic balance is maintained.
503-
# Uses the ClimaCore `column_integral_indefinite!` function to solve
504-
# ∂(ln𝑝)/∂z = -g/(Rₘ(q)T), where
505-
# p is the local pressure
506-
# g is the gravitational constant
507-
# q is the specific humidity
508-
# Rₘ is the gas constant for moist air
509-
# T is the air temperature
510-
# p is then updated with the integral result, given p_sfc,
511-
# following which the thermodynamic state is constructed.
512-
ᶜ∂lnp∂z = @. -thermo_params.grav /
513-
(TD.gas_constant_air(thermo_params, TD.PhasePartition(ᶜq_tot)) * ᶜT)
514-
ᶠlnp_over_psfc = zeros(face_space)
515-
Operators.column_integral_indefinite!(ᶠlnp_over_psfc, ᶜ∂lnp∂z)
516-
ᶠp = p_sfc .* exp.(ᶠlnp_over_psfc)
622+
use_p3d = use_full_pressure && NC.NCDataset(file_path) do ds
623+
haskey(ds, "p_3d")
624+
end
625+
ᶠp = if use_p3d
626+
SpaceVaryingInputs.SpaceVaryingInput(
627+
file_path,
628+
"p_3d",
629+
face_space;
630+
regridder_type,
631+
regridder_kwargs = regridder_kwargs,
632+
)
633+
else
634+
if use_full_pressure
635+
@warn "Requested full pressure initialization, but variable `p_3d` is missing in $(file_path). Falling back to hydrostatic integration from surface pressure."
636+
end
637+
# Using surface pressure, air temperature and specific humidity
638+
# from the dataset, compute air pressure by hydrostatic integration.
639+
p_sfc = Fields.level(
640+
SpaceVaryingInputs.SpaceVaryingInput(
641+
file_path,
642+
"p",
643+
face_space;
644+
regridder_type,
645+
regridder_kwargs = regridder_kwargs,
646+
),
647+
Fields.half,
648+
)
649+
# Apply hydrostatic surface-pressure correction only if surface altitude is available
650+
surface_altitude_var = "z_sfc"
651+
has_surface_altitude = NC.NCDataset(file_path) do ds
652+
haskey(ds, surface_altitude_var)
653+
end
654+
if has_surface_altitude
655+
correct_surface_pressure_for_topography!(
656+
p_sfc,
657+
file_path,
658+
face_space,
659+
Y,
660+
ᶜT,
661+
ᶜq_tot,
662+
thermo_params,
663+
regridder_kwargs;
664+
surface_altitude_var = surface_altitude_var,
665+
)
666+
else
667+
@warn "Skipping topographic correction because variable `$surface_altitude_var` is missing from $(file_path)."
668+
end
669+
# With the known temperature (ᶜT) and moisture (ᶜq_tot) profile,
670+
# recompute the pressure levels assuming hydrostatic balance is maintained.
671+
ᶜ∂lnp∂z = @. -thermo_params.grav /
672+
(TD.gas_constant_air(thermo_params, TD.PhasePartition(ᶜq_tot)) * ᶜT)
673+
ᶠlnp_over_psfc = zeros(face_space)
674+
Operators.column_integral_indefinite!(ᶠlnp_over_psfc, ᶜ∂lnp∂z)
675+
p_sfc .* exp.(ᶠlnp_over_psfc)
676+
end
517677
ᶜts = TD.PhaseEquil_pTq.(thermo_params, ᶜinterp.(ᶠp), ᶜT, ᶜq_tot)
518678

519679
# Assign prognostic variables from equilibrium moisture models
@@ -525,19 +685,22 @@ function overwrite_initial_conditions!(
525685
SpaceVaryingInputs.SpaceVaryingInput(
526686
file_path,
527687
"u",
528-
center_space,
688+
center_space;
689+
regridder_type,
529690
regridder_kwargs = regridder_kwargs,
530691
),
531692
SpaceVaryingInputs.SpaceVaryingInput(
532693
file_path,
533694
"v",
534-
center_space,
695+
center_space;
696+
regridder_type,
535697
regridder_kwargs = regridder_kwargs,
536698
),
537699
SpaceVaryingInputs.SpaceVaryingInput(
538700
file_path,
539701
"w",
540-
center_space,
702+
center_space;
703+
regridder_type,
541704
regridder_kwargs = regridder_kwargs,
542705
),
543706
)
@@ -569,14 +732,16 @@ function overwrite_initial_conditions!(
569732
SpaceVaryingInputs.SpaceVaryingInput(
570733
file_path,
571734
"cswc",
572-
center_space,
735+
center_space;
736+
regridder_type,
573737
regridder_kwargs = regridder_kwargs,
574738
) .* Y.c.ρ
575739
Y.c.ρq_rai .=
576740
SpaceVaryingInputs.SpaceVaryingInput(
577741
file_path,
578742
"crwc",
579-
center_space,
743+
center_space;
744+
regridder_type,
580745
regridder_kwargs = regridder_kwargs,
581746
) .* Y.c.ρ
582747
end

0 commit comments

Comments
 (0)