Skip to content

Commit 32a378a

Browse files
authored
add perturbed ERA5 forcing (#1291)
1 parent 0bcfbc5 commit 32a378a

File tree

2 files changed

+242
-12
lines changed

2 files changed

+242
-12
lines changed

experiments/long_runs/low_res_snowy_land.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,12 @@ function setup_model(FT, start_date, stop_date, Δt, domain, earth_param_set)
6262
context,
6363
lowres = true,
6464
)
65-
atmos, radiation = ClimaLand.prescribed_forcing_era5(
65+
atmos, radiation = ClimaLand.prescribed_perturbed_forcing_era5(
6666
era5_ncdata_path,
6767
surface_space,
6868
start_date,
6969
earth_param_set,
70+
5.0,
7071
FT;
7172
max_wind_speed = 25.0,
7273
time_interpolation_method = LinearInterpolation(PeriodicCalendar()),

src/shared_utilities/drivers.jl

Lines changed: 240 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export AbstractAtmosphericDrivers,
3737
make_update_drivers,
3838
prescribed_lai_era5,
3939
prescribed_forcing_era5,
40+
prescribed_perturbed_forcing_era5,
4041
prescribed_analytic_forcing,
4142
default_zenith_angle
4243

@@ -1031,16 +1032,53 @@ function specific_humidity_from_dewpoint(
10311032
sim_FT(T_dew_air) - _T_freeze,
10321033
sim_FT(T_air) - _T_freeze,
10331034
)
1034-
1035-
# In the future, we will use Thermodynamics functions for all of the below. Note that the type of T_air must match the type of the parameters in thermo_params.
1036-
esat::sim_FT = Thermodynamics.saturation_vapor_pressure(
1035+
q = Thermodynamics.q_vap_from_RH_liquid(
10371036
thermo_params,
1037+
sim_FT(P_air),
10381038
sim_FT(T_air),
1039-
Thermodynamics.Liquid(),
1039+
rh,
10401040
)
1041+
return q
1042+
end
1043+
1044+
1045+
"""
1046+
perturbed_specific_humidity_from_dewpoint(dewpoint_temperature, temperature, air_pressure, earth_param_set, ΔT)
10411047
1042-
e = rh * esat
1043-
q = data_FT(0.622 * e / (P_air - 0.378 * e))
1048+
Estimates the perturbed specific humidity given the true dewpoint temperature, true temperature of the air
1049+
in Kelvin, and true air pressure in Pa, along with a perturbation to the temperature ΔT, and the ClimaLand
1050+
`earth_param_set`. We first compute relative humidity, and then using RH and the perturbed air temperature,
1051+
equal to true temperature+ΔT, compute a perturbed `q`.
1052+
1053+
Relative humidity is computed using the Magnus formula.
1054+
1055+
For more information on the Magnus formula, see e.g.
1056+
Lawrence, Mark G. (1 February 2005).
1057+
"The Relationship between Relative Humidity and the Dewpoint Temperature in Moist Air:
1058+
A Simple Conversion and Applications".
1059+
Bulletin of the American Meteorological Society. 86 (2): 225–234.
1060+
"""
1061+
function perturbed_specific_humidity_from_dewpoint(
1062+
T_dew_air::data_FT,
1063+
T_air::data_FT,
1064+
P_air::data_FT,
1065+
earth_param_set,
1066+
ΔT,
1067+
) where {data_FT <: Real}
1068+
thermo_params = LP.thermodynamic_parameters(earth_param_set)
1069+
_T_freeze = LP.T_freeze(earth_param_set)
1070+
sim_FT = typeof(_T_freeze)
1071+
# Obtain the relative humidity. This function requires temperatures in Celsius
1072+
rh::sim_FT = rh_from_dewpoint(
1073+
sim_FT(T_dew_air) - _T_freeze,
1074+
sim_FT(T_air) - _T_freeze,
1075+
)
1076+
q = Thermodynamics.q_vap_from_RH_liquid(
1077+
thermo_params,
1078+
sim_FT(P_air),
1079+
sim_FT(T_air + ΔT),
1080+
rh,
1081+
)
10441082
return q
10451083
end
10461084

@@ -1049,6 +1087,8 @@ end
10491087
10501088
Returns the relative humidity given the dewpoint temperature in Celsius and the
10511089
air temperature in Celsius, using the Magnus formula.
1090+
Since this formula is not restricted to the [0,1] range,
1091+
we enforce it.
10521092
10531093
For more information on the Magnus formula, see e.g.
10541094
Lawrence, Mark G. (1 February 2005).
@@ -1061,7 +1101,7 @@ function rh_from_dewpoint(Td_C::FT, T_C::FT) where {FT <: Real}
10611101
b = FT(17.625) # unitless
10621102
γ = Td_C * b / (c + Td_C)
10631103
rh = exp- b * T_C / (c + T_C))
1064-
return rh
1104+
return max(FT(0), min(rh, FT(1.0)))
10651105
end
10661106

10671107
"""
@@ -1499,27 +1539,28 @@ function prescribed_forcing_era5(
14991539
# Pass a list of files in all cases
15001540
era5_ncdata_path isa String && (era5_ncdata_path = [era5_ncdata_path])
15011541

1502-
# Precip is provide as a mass flux; convert to volume flux of liquid water with ρ =1000 kg/m^3
1542+
_ρ_liq = LP.ρ_cloud_liq(earth_param_set)
1543+
# Precip is provided as a mass flux; convert to volume flux of liquid water with ρ = 1000 kg/m^3
15031544
precip = TimeVaryingInput(
15041545
[era5_ncdata_path, era5_ncdata_path],
15051546
["mtpr", "msr"],
15061547
surface_space;
15071548
start_date,
15081549
regridder_type,
15091550
regridder_kwargs = (; interpolation_method),
1510-
file_reader_kwargs = (; preprocess_func = (data) -> -data / 1000,),
1551+
file_reader_kwargs = (; preprocess_func = (data) -> -data / _ρ_liq,),
15111552
method = time_interpolation_method,
15121553
compose_function = (mtpr, msr) -> min.(mtpr .- msr, Float32(0)),
15131554
)
1514-
# Precip is provide as a mass flux; convert to volume flux of liquid water with ρ =1000 kg/m^3
1555+
# Precip is provided as a mass flux; convert to volume flux of liquid water with ρ = 1000 kg/m^3
15151556
snow_precip = TimeVaryingInput(
15161557
era5_ncdata_path,
15171558
"msr",
15181559
surface_space;
15191560
start_date,
15201561
regridder_type,
15211562
regridder_kwargs = (; interpolation_method),
1522-
file_reader_kwargs = (; preprocess_func = (data) -> -data / 1000,),
1563+
file_reader_kwargs = (; preprocess_func = (data) -> -data / _ρ_liq,),
15231564
method = time_interpolation_method,
15241565
)
15251566
if max_wind_speed isa Nothing
@@ -1642,6 +1683,194 @@ function prescribed_forcing_era5(
16421683
return (; atmos, radiation)
16431684
end
16441685

1686+
"""
1687+
prescribed_perturbed_forcing_era5(era5_ncdata_path,
1688+
surface_space,
1689+
start_date,
1690+
earth_param_set,
1691+
ΔT,
1692+
FT;
1693+
gustiness=1,
1694+
max_wind_speed = nothing,
1695+
c_co2 = TimeVaryingInput((t) -> 4.2e-4),
1696+
time_interpolation_method = LinearInterpolation(PeriodicCalendar()),
1697+
regridder_type = :InterpolationsRegridder,
1698+
interpolation_method = Interpolations.Constant(),)
1699+
1700+
A helper function which constructs the `PrescribedAtmosphere` and `PrescribedRadiativeFluxes`
1701+
from a file path pointing to the ERA5 data in a netcdf file, the surface_space, the start date,
1702+
and the earth_param_set, applying a change in the instantaneous temperature at each point
1703+
of ΔT, while keeping the relative humidity fixed.
1704+
1705+
Please see the documentation for `prescribed_forcing_era5` for more information.
1706+
"""
1707+
function prescribed_perturbed_forcing_era5(
1708+
era5_ncdata_path,
1709+
surface_space,
1710+
start_date,
1711+
earth_param_set,
1712+
ΔT,
1713+
FT;
1714+
gustiness = 1,
1715+
max_wind_speed = nothing,
1716+
c_co2 = TimeVaryingInput((t) -> 4.2e-4),
1717+
time_interpolation_method = LinearInterpolation(PeriodicCalendar()),
1718+
regridder_type = :InterpolationsRegridder,
1719+
interpolation_method = Interpolations.Constant(),
1720+
)
1721+
# Pass a list of files in all cases
1722+
era5_ncdata_path isa String && (era5_ncdata_path = [era5_ncdata_path])
1723+
_ρ_liq = LP.ρ_cloud_liq(earth_param_set)
1724+
# Precip is provided as a mass flux; convert to volume flux of liquid water with ρ = 1000 kg/m^3
1725+
precip = TimeVaryingInput(
1726+
[era5_ncdata_path, era5_ncdata_path],
1727+
["mtpr", "msr"],
1728+
surface_space;
1729+
start_date,
1730+
regridder_type,
1731+
regridder_kwargs = (; interpolation_method),
1732+
file_reader_kwargs = (; preprocess_func = (data) -> -data / _ρ_liq,),
1733+
method = time_interpolation_method,
1734+
compose_function = (mtpr, msr) -> min.(mtpr .- msr, Float32(0)),
1735+
)
1736+
# Precip is provided as a mass flux; convert to volume flux of liquid water with ρ = 1000 kg/m^3
1737+
snow_precip = TimeVaryingInput(
1738+
era5_ncdata_path,
1739+
"msr",
1740+
surface_space;
1741+
start_date,
1742+
regridder_type,
1743+
regridder_kwargs = (; interpolation_method),
1744+
file_reader_kwargs = (; preprocess_func = (data) -> -data / _ρ_liq,),
1745+
method = time_interpolation_method,
1746+
)
1747+
if max_wind_speed isa Nothing
1748+
wind_function = (u, v) -> sqrt.(u .^ 2 .+ v .^ 2)
1749+
else
1750+
wind_function = (u, v) -> min.(sqrt.(u .^ 2 .+ v .^ 2), max_wind_speed)
1751+
end
1752+
1753+
u_atmos = TimeVaryingInput(
1754+
[era5_ncdata_path, era5_ncdata_path],
1755+
["u10", "v10"],
1756+
surface_space;
1757+
start_date,
1758+
regridder_type,
1759+
regridder_kwargs = (; interpolation_method),
1760+
compose_function = wind_function,
1761+
method = time_interpolation_method,
1762+
)
1763+
specific_humidity(Td, T, P; params = earth_param_set, ΔT = ΔT) =
1764+
ClimaLand.perturbed_specific_humidity_from_dewpoint.(
1765+
Td,
1766+
T,
1767+
P,
1768+
params,
1769+
ΔT,
1770+
)
1771+
q_atmos = TimeVaryingInput(
1772+
[era5_ncdata_path, era5_ncdata_path, era5_ncdata_path],
1773+
["d2m", "t2m", "sp"],
1774+
surface_space;
1775+
start_date,
1776+
regridder_type,
1777+
regridder_kwargs = (; interpolation_method),
1778+
compose_function = specific_humidity,
1779+
method = time_interpolation_method,
1780+
)
1781+
P_atmos = TimeVaryingInput(
1782+
era5_ncdata_path,
1783+
"sp",
1784+
surface_space;
1785+
start_date,
1786+
regridder_type,
1787+
regridder_kwargs = (; interpolation_method),
1788+
method = time_interpolation_method,
1789+
)
1790+
perturb_temp(T; ΔT = ΔT) = T .+ ΔT
1791+
T_atmos = TimeVaryingInput(
1792+
era5_ncdata_path,
1793+
"t2m",
1794+
surface_space;
1795+
start_date,
1796+
regridder_type,
1797+
regridder_kwargs = (; interpolation_method),
1798+
file_reader_kwargs = (; preprocess_func = perturb_temp),
1799+
method = time_interpolation_method,
1800+
)
1801+
h_atmos = FT(10)
1802+
1803+
atmos = PrescribedAtmosphere(
1804+
precip,
1805+
snow_precip,
1806+
T_atmos,
1807+
u_atmos,
1808+
q_atmos,
1809+
P_atmos,
1810+
start_date,
1811+
h_atmos,
1812+
earth_param_set;
1813+
gustiness = FT(gustiness),
1814+
c_co2 = c_co2,
1815+
)
1816+
1817+
SW_d = TimeVaryingInput(
1818+
era5_ncdata_path,
1819+
"msdwswrf",
1820+
surface_space;
1821+
start_date,
1822+
regridder_type,
1823+
regridder_kwargs = (; interpolation_method),
1824+
method = time_interpolation_method,
1825+
)
1826+
function compute_diffuse_fraction(total, direct)
1827+
diff = max(total - direct, Float32(0))
1828+
return min(diff / (total + eps(Float32)), Float32(1))
1829+
end
1830+
function compute_diffuse_fraction_broadcasted(total, direct)
1831+
return @. compute_diffuse_fraction(total, direct)
1832+
end
1833+
1834+
frac_diff = TimeVaryingInput(
1835+
[era5_ncdata_path, era5_ncdata_path],
1836+
["msdwswrf", "msdrswrf"],
1837+
surface_space;
1838+
start_date,
1839+
regridder_type,
1840+
regridder_kwargs = (; interpolation_method),
1841+
method = time_interpolation_method,
1842+
compose_function = compute_diffuse_fraction_broadcasted,
1843+
)
1844+
LW_d = TimeVaryingInput(
1845+
era5_ncdata_path,
1846+
"msdwlwrf",
1847+
surface_space;
1848+
start_date,
1849+
regridder_type,
1850+
regridder_kwargs = (; interpolation_method),
1851+
method = time_interpolation_method,
1852+
)
1853+
zenith_angle =
1854+
(t, s) -> default_zenith_angle(
1855+
t,
1856+
s;
1857+
latitude = ClimaCore.Fields.coordinate_field(surface_space).lat,
1858+
longitude = ClimaCore.Fields.coordinate_field(surface_space).long,
1859+
insol_params = earth_param_set.insol_params,
1860+
)
1861+
1862+
radiation = PrescribedRadiativeFluxes(
1863+
FT,
1864+
SW_d,
1865+
LW_d,
1866+
start_date;
1867+
θs = zenith_angle,
1868+
earth_param_set = earth_param_set,
1869+
frac_diff = frac_diff,
1870+
)
1871+
return (; atmos, radiation)
1872+
end
1873+
16451874

16461875
"""
16471876
prescribed_lai_era5(era5_lai_ncdata_path,

0 commit comments

Comments
 (0)