Skip to content

Commit 989b07c

Browse files
authored
Adds a snow albedo which varies with zenith angle (#1100)
* add albedo option to snow * address ordering issue
1 parent 0d0b4ee commit 989b07c

File tree

12 files changed

+148
-42
lines changed

12 files changed

+148
-42
lines changed

.dev/Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
33

44
[compat]
5-
JuliaFormatter = "1"
5+
JuliaFormatter = "1"
6+

docs/src/APIs/Snow.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,11 @@ ClimaLand.Snow.snow_cover_fraction
3131
ClimaLand.Snow.snow_boundary_fluxes!
3232
ClimaLand.Snow.phase_change_flux
3333
ClimaLand.Snow.AtmosDrivenSnowBC
34-
```
34+
```
35+
36+
## Snow parameterizations
37+
38+
```@docs
39+
ClimaLand.Snow.ZenithAngleAlbedoModel
40+
ClimaLand.Snow.ConstantAlbedoModel
41+
```

experiments/integrated/fluxnet/snow_soil/simulation.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ soil_model_type = Soil.EnergyHydrology
8787
α = 0.8
8888
snow_parameters = SnowParameters{FT}(
8989
dt;
90-
α_snow = α,
90+
α_snow = Snow.ConstantAlbedoModel(α),
9191
density = Snow.MinimumDensityModel(ρ),
9292
earth_param_set = earth_param_set,
9393
);

experiments/long_runs/snowy_land.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,13 @@ function setup_prob(
312312
)
313313

314314
# Snow model
315-
snow_parameters = SnowParameters{FT}(Δt; earth_param_set = earth_param_set)
315+
# α_snow = Snow.ConstantAlbedoModel(FT(0.7))
316+
α_snow = Snow.ZenithAngleAlbedoModel(FT(0.5), FT(0.9), FT(1))
317+
snow_parameters = SnowParameters{FT}(
318+
Δt;
319+
earth_param_set = earth_param_set,
320+
α_snow = α_snow,
321+
)
316322
snow_args = (;
317323
parameters = snow_parameters,
318324
domain = ClimaLand.obtain_surface_domain(domain),

experiments/standalone/Snow/snowmip_simulation.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ density_model = NeuralSnow.NeuralDepthModel(FT)
5555

5656
parameters = SnowParameters{FT}(
5757
Δt;
58-
α_snow = α,
58+
α_snow = Snow.ConstantAlbedoModel(α),
5959
density = density_model,
6060
earth_param_set = param_set,
6161
)

src/integrated/land.jl

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,7 @@ function LandModel{FT}(;
128128
)
129129

130130
transpiration = Canopy.PlantHydraulics.DiagnosticTranspiration{FT}()
131-
ground_conditions =
132-
PrognosticGroundConditions{FT, typeof(snow.parameters.α_snow)}(
133-
snow.parameters.α_snow,
134-
)
131+
ground_conditions = PrognosticGroundConditions()
135132
if :energy in propertynames(canopy_component_args)
136133
energy_model = canopy_component_types.energy(
137134
canopy_component_args.energy.parameters,
@@ -179,6 +176,20 @@ function LandModel{FT}(;
179176
return LandModel{FT, typeof.(args)...}(args...)
180177
end
181178

179+
"""
180+
ClimaLand.land_components(land::LandModel)
181+
182+
Returns the components of the `LandModel`.
183+
184+
Currently, this method is required in order to preserve an ordering in how
185+
we update the component models' auxiliary states. The canopy update_aux! step
186+
depends on snow and soil albedo, but those are only updated in the snow and soil
187+
update_aux! steps. So those must occur first (as controlled by the order of the components
188+
returned by `land_components!`.
189+
190+
This needs to be fixed.
191+
"""
192+
ClimaLand.land_components(land::LandModel) = (:soil, :snow, :soilco2, :canopy)
182193
"""
183194
lsm_aux_vars(m::LandModel)
184195
@@ -416,8 +427,8 @@ function lsm_radiant_energy_fluxes!(
416427
ϵ_soil = land.soil.parameters.emissivity
417428
T_soil = ClimaLand.Domains.top_center_to_surface(p.soil.T)
418429

419-
α_snow_NIR = land.snow.parameters.α_snow
420-
α_snow_PAR = land.snow.parameters.α_snow
430+
α_snow_NIR = p.snow.α_snow
431+
α_snow_PAR = p.snow.α_snow
421432
ϵ_snow = land.snow.parameters.ϵ_snow
422433
T_snow = p.snow.T_sfc
423434

@@ -579,25 +590,16 @@ function ClimaLand.Soil.sublimation_source(
579590
end
580591

581592
"""
582-
PrognosticGroundConditions{FT <: AbstractFloat, F <: Union{FT, ClimaCore.Fields.Field}} <: Canopy.AbstractGroundConditions
593+
PrognosticGroundConditions <: Canopy.AbstractGroundConditions
583594
584595
A type of Canopy.AbstractGroundConditions to use when the soil model is prognostic and
585596
of type `EnergyHydrology`, and the snow model is prognostic and included.
586597
587-
The canopy model needs albedo of the ground
588-
in order to compute its update_aux! function, and that function must only depend on the canopy model.
589-
Because of this, α_snow must be stored in this struct until it is stored in the cache.
590-
591-
Note that this struct is linked with the EnergyHydrology model. If we ever had a different
598+
Note that this struct is linked with the EnergyHydrology/SnowModel model. If we ever had a different
592599
soil model, we might need to construct a different `PrognosticGroundConditions` because
593600
the fields may be stored in different places.
594601
"""
595-
struct PrognosticGroundConditions{
596-
FT <: AbstractFloat,
597-
F <: Union{FT, ClimaCore.Fields.Field},
598-
} <: Canopy.AbstractGroundConditions
599-
α_snow::FT
600-
end
602+
struct PrognosticGroundConditions <: Canopy.AbstractGroundConditions end
601603

602604
"""
603605
Canopy.ground_albedo_PAR(
@@ -620,7 +622,7 @@ function Canopy.ground_albedo_PAR(
620622
)
621623
@. p.α_ground.PAR =
622624
(1 - p.snow.snow_cover_fraction) * p.soil.PAR_albedo +
623-
p.snow.snow_cover_fraction * ground.α_snow
625+
p.snow.snow_cover_fraction * p.snow.α_snow
624626
return p.α_ground.PAR
625627
end
626628

@@ -645,7 +647,7 @@ function Canopy.ground_albedo_NIR(
645647
)
646648
@. p.α_ground.NIR =
647649
(1 - p.snow.snow_cover_fraction) * p.soil.NIR_albedo +
648-
p.snow.snow_cover_fraction * ground.α_snow
650+
p.snow.snow_cover_fraction * p.snow.α_snow
649651
return p.α_ground.NIR
650652
end
651653

src/shared_utilities/models.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,12 +484,16 @@ This can be converted to a mass using the density of liquid water.
484484
This includes the water in multiple phases. For example, if ice is present, the water
485485
volume is computed using ratio of the density of ice to the density of liquid water.
486486
"""
487-
function total_liq_water_vol_per_area!(cache, model::AbstractModel, Y, p, t) end
487+
function total_liq_water_vol_per_area!(cache, model::AbstractModel, Y, p, t)
488+
cache .= 0
489+
end
488490

489491
"""
490492
total_energy_per_area!(cache, model::AbstractModel, Y, p, t)
491493
492494
A function which updates `cache` in place with the total energy
493495
per unit ground area for the `model`, computed from `Y`, `p`, and `t`.
494496
"""
495-
function total_energy_per_area!(cache, model::AbstractModel, Y, p, t) end
497+
function total_energy_per_area!(cache, model::AbstractModel, Y, p, t)
498+
cache .= 0
499+
end

src/standalone/Snow/Snow.jl

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ import ClimaLand:
3333
get_drivers,
3434
total_energy_per_area!,
3535
total_liq_water_vol_per_area!
36-
export SnowParameters, SnowModel, AtmosDrivenSnowBC, snow_boundary_fluxes!
36+
export SnowParameters,
37+
SnowModel,
38+
AtmosDrivenSnowBC,
39+
snow_boundary_fluxes!,
40+
ConstantAlbedoModel,
41+
ZenithAngleAlbedoModel
3742

3843
"""
3944
AbstractSnowModel{FT} <: ClimaLand.AbstractExpModel{FT}
@@ -73,6 +78,50 @@ struct MinimumDensityModel{FT} <: AbstractDensityModel{FT}
7378
end
7479

7580

81+
"""
82+
AbstractAlbedoModel{FT}
83+
84+
Defines the model type for albedo parameterization
85+
for use within an `AbstractSnowModel` type.
86+
87+
These parameterizations are stored in parameters.α_snow, and
88+
are used to update the value of p.snow.α_snow (the broadband
89+
albedo of the snow at a point).
90+
stored
91+
"""
92+
abstract type AbstractAlbedoModel{FT <: AbstractFloat} end
93+
94+
"""
95+
ConstantAlbedoModel{FT <: AbstractFloat} <: AbstractAlbedoModel{FT}
96+
97+
Establishes the albedo parameterization where albedo is treated as a
98+
constant spatially and temporally.
99+
"""
100+
struct ConstantAlbedoModel{FT} <: AbstractAlbedoModel{FT}
101+
"Albedo of snow (unitless)"
102+
α::FT
103+
end
104+
105+
"""
106+
ZenithAngleAlbedoModel{FT <: AbstractFloat} <: AbstractAlbedoModel{FT}
107+
108+
Establishes the albedo parameterization where albedo only
109+
depends on the cosine of the zenith angle of the sun, as
110+
α = α_0 + (α_horizon - α_0)*exp(-k*cos(θs))
111+
112+
Note: If this choice is used, the field cosθs must appear in the cache
113+
p.drivers. This is available through the PrescribedRadiativeFluxes object.
114+
"""
115+
struct ZenithAngleAlbedoModel{FT} <: AbstractAlbedoModel{FT}
116+
"Free parameter controlling the minimum snow albedo"
117+
α_0::FT
118+
"The albedo of snow when the sun is at the horizon"
119+
α_horizon::FT
120+
"Rate at which albedo drops from α_horizon to its minimum value"
121+
k::FT
122+
end
123+
124+
76125
"""
77126
SnowParameters{FT <: AbstractFloat, PSE}
78127
@@ -90,6 +139,7 @@ $(DocStringExtensions.FIELDS)
90139
Base.@kwdef struct SnowParameters{
91140
FT <: AbstractFloat,
92141
DM <: AbstractDensityModel,
142+
AM <: AbstractAlbedoModel,
93143
PSE,
94144
}
95145
"Choice of parameterization for snow density"
@@ -98,8 +148,8 @@ Base.@kwdef struct SnowParameters{
98148
z_0m::FT
99149
"Roughness length over snow for scalars (m)"
100150
z_0b::FT
101-
"Albedo of snow (unitless)"
102-
α_snow::FT
151+
"Albedo parameterization for snow"
152+
α_snow::AM
103153
"Emissivity of snow (unitless)"
104154
ϵ_snow::FT
105155
"Volumetric holding capacity of water in snow (unitless)"
@@ -121,7 +171,7 @@ end
121171
density = MinimumDensityModel(200),
122172
z_0m = FT(0.0024),
123173
z_0b = FT(0.00024),
124-
α_snow = FT(0.8),
174+
α_snow = ConstantAlbedoModel(FT(0.8)),
125175
ϵ_snow = FT(0.99),
126176
θ_r = FT(0.08),
127177
Ksat = FT(1e-3),
@@ -137,15 +187,20 @@ function SnowParameters{FT}(
137187
density::DM = MinimumDensityModel(FT(200)),
138188
z_0m = FT(0.0024),
139189
z_0b = FT(0.00024),
140-
α_snow = FT(0.8),
190+
α_snow::AM = ConstantAlbedoModel(FT(0.8)),
141191
ϵ_snow = FT(0.99),
142192
θ_r = FT(0.08),
143193
Ksat = FT(1e-3),
144194
κ_ice = FT(2.21),
145195
ΔS = FT(0.1),
146196
earth_param_set::PSE,
147-
) where {FT <: AbstractFloat, DM <: AbstractDensityModel, PSE}
148-
return SnowParameters{FT, DM, PSE}(
197+
) where {
198+
FT <: AbstractFloat,
199+
DM <: AbstractDensityModel,
200+
AM <: AbstractAlbedoModel,
201+
PSE,
202+
}
203+
return SnowParameters{FT, DM, AM, PSE}(
149204
density,
150205
z_0m,
151206
z_0b,
@@ -273,6 +328,7 @@ auxiliary_vars(snow::SnowModel) = (
273328
:T,
274329
:T_sfc,
275330
:z_snow,
331+
:α_snow,
276332
:ρ_snow,
277333
:R_n,
278334
:phase_change_flux,
@@ -305,6 +361,7 @@ auxiliary_types(snow::SnowModel{FT}) where {FT} = (
305361
FT,
306362
FT,
307363
FT,
364+
FT,
308365
boundary_var_types(
309366
snow,
310367
snow.boundary_conditions,
@@ -330,6 +387,7 @@ auxiliary_domain_names(snow::SnowModel) = (
330387
:surface,
331388
:surface,
332389
:surface,
390+
:surface,
333391
boundary_var_domain_names(
334392
snow.boundary_conditions,
335393
ClimaLand.TopBoundary(),
@@ -342,6 +400,7 @@ ClimaLand.name(::SnowModel) = :snow
342400
function ClimaLand.make_update_aux(model::SnowModel{FT}) where {FT}
343401
function update_aux!(p, Y, t)
344402
parameters = model.parameters
403+
update_snow_albedo!(p.snow.α_snow, parameters.α_snow, Y, p, t)
345404

346405
# This has to happen first, since other quantities below depend
347406
# on it.

src/standalone/Snow/snow_parameterizations.jl

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,31 @@ export snow_surface_temperature,
1010
energy_from_T_and_swe,
1111
snow_cover_fraction,
1212
snow_bulk_density,
13-
phase_change_flux
13+
phase_change_flux,
14+
update_snow_albedo!
1415

16+
"""
17+
update_snow_albedo!(α, m::ConstantAlbedoModel, Y, p, t)
18+
19+
Updates the snow albedo `α` in place with the current albedo,
20+
according to the ConstantAlbedoModel.
21+
"""
22+
function update_snow_albedo!(α, m::ConstantAlbedoModel, Y, p, t)
23+
@. α = m.α
24+
end
25+
26+
"""
27+
update_snow_albedo!(α, m::ZenithAngleAlbedoModel, Y, p, t)
28+
29+
Updates the snow albedo `α` in place with the current albedo,
30+
according to the ZenithAngleAlbedoModel.
31+
"""
32+
function update_snow_albedo!(α, m::ZenithAngleAlbedoModel, Y, p, t)
33+
FT = FTfromY(Y)
34+
@. α =
35+
m.α_0 +
36+
(m.α_horizon - m.α_0) * exp(-m.k * max(p.drivers.cosθs, eps(FT)))
37+
end
1538
"""
1639
snow_cover_fraction(x::FT; z0 = FT(1e-1), α = FT(2))::FT where {FT}
1740
@@ -54,8 +77,8 @@ end
5477
5578
A helper function which computes and returns the snow albedo.
5679
"""
57-
function ClimaLand.surface_albedo(model::SnowModel, _...)
58-
return model.parameters.α_snow
80+
function ClimaLand.surface_albedo(model::SnowModel, Y, p)
81+
return p.snow.α_snow
5982
end
6083

6184
"""

test/integrated/full_land.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ f_over = FT(3.28) # 1/m
1919
R_sb = FT(1.484e-4 / 1000) # m/s
2020
scalar_soil_params = (; f_over, R_sb)
2121

22-
α_snow = FT(0.67)
22+
α_snow = Snow.ConstantAlbedoModel(FT(0.67))
2323
scalar_snow_params = (; α_snow, Δt)
2424

2525
# Energy Balance model
@@ -82,6 +82,7 @@ land = global_land_model(
8282
LAI = LAI,
8383
)
8484

85+
@test ClimaLand.land_components(land) == (:soil, :snow, :soilco2, :canopy)
8586
Y, p, cds = initialize(land)
8687

8788
# Soil IC

0 commit comments

Comments
 (0)