Skip to content

Conversation

@costachris
Copy link
Member

@costachris costachris commented Nov 12, 2025

Adjust for ERA5/ClimaAtmos topo differences when initializing pressure. Account for lapse rate in mslp sea level reduction (diagnostics).
The colorbars vary, but note each improvement significantly reduces the RMSE/Bias.
Current 😭
mslp_v1
Topography correction in surface pressure init 👀
mslp_v2
Topography correction + account for lapse rate in MSLP diagnostic 🥳
mslp_v3

@costachris costachris requested a review from Julians42 November 12, 2025 23:48
…re. Account for lapse rate in mslp sea level reduction (diagnostics).
@costachris costachris force-pushed the cc/improve_wxmodel_pressure_init_diag branch from 6f20da6 to 1ce4418 Compare November 13, 2025 00:47
@assert all(map(x -> x in (keys(ncin)), in_dims)) "Source file $source_file is missing subset of the required dimensions: $in_dims"

# assert ncin has required variables
req_vars = ["u", "v", "w", "t", "q", "skt", "sp"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it breaks without surface_geopotential catch it with the @assert here

# Choose attributes; for z_sfc, set clean altitude attributes
var_attrib = if dst_name == "z_sfc"
Dict(
"standard_name" => "surface_altitude",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the data you're writing is geopotential based on the ERA5 name? If that is the case the units are gravity * height (m^2/s^2) and would be good to at least rename the long_name and the units. I would also use zg_sfc even though ERA5 uses z for geopotential because then it's very clear.

Copy link
Member

@Julians42 Julians42 Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok I see what is happening now, you're correct. Not sure I have an immediate suggestion other that it is a little confusing because there are two if statements in this loop to catch z_sfc since it needs a new var_attrib and needs to be divided by gravity. Possibly separating it out could be cleaner / easier to read if a little longer.

# Convert geopotential to meters if necessary
if dst_name == "z_sfc"
data2d .= data2d ./ grav
end
Copy link
Member

@Julians42 Julians42 Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
end
end
# Duplicate 2D surface field across all target vertical levels

I think this comment would be helpful here

end
var_obj = defVar(ncout, dst_name, FT, ("lon", "lat", "z"), attrib = var_attrib)
# Read first time slice and coalesce; follow same convention as sp (use [:, :, 1])
data2d = FT.(coalesce.(ncin[src_name][:, :, 1], NaN))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a coalesce? Are there instances when the data is NaN and our model will still run? If not then we could consider asserting not NaN

Comment on lines +438 to +441
if !has_z_sfc
@warn "Skipping topographic correction because variable `$var_name` is missing from $(file_path)."
return false
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this haskey check is maybe better done in overwrite_initial_conditions!? Perhaps also passing the variable name z_sfc as a function argument would make it more generalizable

Comment on lines +468 to +470
z_surface_arr = Array(Fields.field2array(ᶠz_surface))
z_model_arr = Array(Fields.field2array(ᶠz_model_surface))
Δz_arr = Array(Fields.field2array(ᶠΔz))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't field2array already return an array - i.e., does Array() actually do anything here?

Comment on lines +485 to +498
correction_factor = similar(ᶠΔz)
@. correction_factor = ifelse(
isfinite(ᶠΔz),
exp(FT(-1) * ᶠΔz * grav / denom),
one(FT),
)

@. p_sfc =
p_sfc *
ifelse(
isfinite(ᶠΔz),
exp(FT(-1) * ᶠΔz * grav / denom),
one(FT),
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated code. either delete the first bit or simplify the second

Comment on lines +480 to +483
denom = copy(ᶠR_m_sfc)
@. denom = ᶠR_m_sfc * ᶠT_sfc
denom_floor = FT(10)
@. denom = ifelse(isfinite(denom) && denom > denom_floor, denom, denom_floor)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is written confusingly. Could you either comment to explain why we need so many steps (e.g. why is floor and finite checks needed?) Why is 10 chosen as a floor?

Comment on lines +460 to +461
p_sfc_arr = Array(Fields.field2array(p_sfc))
p_sfc_finite = filter(isfinite, p_sfc_arr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again is Array needed after field2array? Is isfinite needed if its surface pressure - shouldn't it be finite? I also think one line and a clearer comment would help

long_name = "Mean Sea Level Pressure",
standard_name = "mean_sea_level_pressure",
units = "Pa",
comments = "Mean sea level pressure computed from the hypsometric equation",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe update the comment to note that this formulation is similar to ERA5 and uses a lapse-rate temperature dependent hypsometric calculation instead of just the standard one

Comment on lines +1672 to +1677
bracket = similar(t_level)
@. bracket = max(ϵ, 1 + Γ * z_level / t_level)

# exponent = g / (R_m Γ)
exponent = similar(R_m_surf)
@. exponent = g / (R_m_surf * Γ)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will allocate new arrays which is slow. Maybe @dennisYatunin could recommend a fix?

z_level = Fields.level(Fields.coordinate_field(state.c.ρ).z, 1)

# compute sea level pressure using the hypsometric equation
# Reduce to mean sea level using a standard-lapse hypsometric formulation (ERA-style)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you write out the whole equation here for clarity? Then it matters less how you break up the computation for performance. e.g., "p_msl = p(z1)(1+\Gamma z1 / t)^(g/(Rm*\Gamma) is the ERA5 standard calculation for MSLP...".

Copy link
Member

@Julians42 Julians42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments to improve clarity and then one performance optimization we should do in the diagnostics for Dennis. Glad this is looking so much better!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants