Skip to content

Commit 8286a1e

Browse files
committed
Cleanup
1 parent e7c2e20 commit 8286a1e

File tree

3 files changed

+56
-9
lines changed

3 files changed

+56
-9
lines changed

src/air_saturation_functions.jl

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -390,9 +390,19 @@ Derivative of saturation vapor specific humidity with respect to temperature.
390390
# Returns
391391
- `∂q_v^* / ∂T`: Derivative of saturation specific humidity with respect to temperature [kg/kg/K].
392392
393-
Computed via the Clausius-Clapeyron relation: `∂q_sat/∂T = q_sat * (L / (Rv * T^2) - 1 / T)`, which
394-
follows from `q_sat = p_sat / (ρ * R_v * T)` and the Clausius-Clapeyron relation
395-
`∂ln(p_sat)/∂T = L / (Rv * T^2)` by differentiation with respect to `T` while holding `ρ` constant.
393+
Computed via the Clausius-Clapeyron relation:
394+
```math
395+
\frac{\partial q_v^*}{\partial T} = q_v^* \left( \frac{L}{R_v T^2} - \frac{1}{T} \right)
396+
```
397+
which follows from the definition of saturation specific humidity
398+
```math
399+
q_v^* = \frac{p_v^*}{\rho R_v T}
400+
```
401+
(where `` q_v^* `` is `q_vap_sat` and `` p_v^* `` is saturation vapor pressure) and the Clausius-Clapeyron relation
402+
```math
403+
\frac{\partial \ln p_v^*}{\partial T} = \frac{L}{R_v T^2}
404+
```
405+
by differentiation with respect to `` T `` while holding `` \rho `` constant.
396406
397407
If `q_liq` and `q_ice` are provided, the saturation specific humidity derivative is computed
398408
assuming a phase mixture defined by the liquid fraction (see [`liquid_fraction`](@ref)).
@@ -407,16 +417,14 @@ parameterization (see [`liquid_fraction_ramp`](@ref)).
407417
q_vap_sat = q_vap_saturation(param_set, T, ρ)
408418
λ = liquid_fraction_ramp(param_set, T)
409419
L = latent_heat_mixed(param_set, T, λ)
410-
R_v = TP.R_v(param_set)
411-
return q_vap_sat * (L / (R_v * T^2) - 1 / T)
420+
return _∂q_vap_sat_∂T_from_L(param_set, q_vap_sat, L, T)
412421
end
413422

414423
@inline function ∂q_vap_sat_∂T(param_set::APS, T, ρ, q_liq, q_ice)
415424
q_vap_sat = q_vap_saturation(param_set, T, ρ, q_liq, q_ice)
416425
λ = liquid_fraction(param_set, T, q_liq, q_ice)
417426
L = latent_heat_mixed(param_set, T, λ)
418-
R_v = TP.R_v(param_set)
419-
return q_vap_sat * (L / (R_v * T^2) - 1 / T)
427+
return _∂q_vap_sat_∂T_from_L(param_set, q_vap_sat, L, T)
420428
end
421429

422430
@inline function ∂q_vap_sat_∂T(param_set::APS, T, ρ, phase::Phase)
@@ -426,6 +434,10 @@ end
426434
latent_heat_vapor(param_set, T),
427435
latent_heat_sublim(param_set, T),
428436
)
437+
return _∂q_vap_sat_∂T_from_L(param_set, q_vap_sat, L, T)
438+
end
439+
440+
@inline function _∂q_vap_sat_∂T_from_L(param_set::APS, q_vap_sat, L, T)
429441
R_v = TP.R_v(param_set)
430442
return q_vap_sat * (L / (R_v * T^2) - 1 / T)
431443
end
@@ -628,4 +640,3 @@ If the specific humidities are not given, the result is the saturation vapor pre
628640
vapor_pressure_deficit(param_set, T, p, q_tot, q_liq, q_ice, Ice()),
629641
)
630642
end
631-

src/saturation_adjustment.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1215,4 +1215,3 @@ calculating necessary intermediate variables.
12151215
e_int_sat = internal_energy_sat(param_set, T, ρ, q_tot)
12161216
return ∂e_int_∂T(param_set, T, e_int_sat, ρ, q_tot, q_vap_sat)
12171217
end
1218-

test/ad_tests.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,41 @@ using Thermodynamics: ρe, pe, ph
295295
end
296296
end
297297
end
298+
299+
@testset "∂q_vap_sat_∂T method consistency" begin
300+
# Test that the different method signatures return consistent values
301+
# Above freezing: Phase(Liquid()) should match (q_liq=0, q_ice=0) which defaults to liquid
302+
# Below freezing: Phase(Ice()) should match (q_liq=0, q_ice=0) which defaults to ice
303+
304+
ρ = FT(1.0)
305+
306+
# Above freezing (T > T_freeze): expects liquid saturation
307+
T_above = FT(290.0)
308+
dq_dT_default = TD.∂q_vap_sat_∂T(param_set, T_above, ρ)
309+
dq_dT_explicit = TD.∂q_vap_sat_∂T(param_set, T_above, ρ, FT(0), FT(0))
310+
dq_dT_liquid = TD.∂q_vap_sat_∂T(param_set, T_above, ρ, TD.Liquid())
311+
312+
@test isapprox(dq_dT_default, dq_dT_explicit; rtol = FT(1e-10))
313+
@test isapprox(dq_dT_default, dq_dT_liquid; rtol = FT(1e-10))
314+
315+
# Below freezing (T < T_icenuc): expects ice saturation
316+
T_below = FT(220.0)
317+
dq_dT_default_cold = TD.∂q_vap_sat_∂T(param_set, T_below, ρ)
318+
dq_dT_explicit_cold = TD.∂q_vap_sat_∂T(param_set, T_below, ρ, FT(0), FT(0))
319+
dq_dT_ice = TD.∂q_vap_sat_∂T(param_set, T_below, ρ, TD.Ice())
320+
321+
@test isapprox(dq_dT_default_cold, dq_dT_explicit_cold; rtol = FT(1e-10))
322+
@test isapprox(dq_dT_default_cold, dq_dT_ice; rtol = FT(1e-10))
323+
324+
# Mixed phase region (T_icenuc < T < T_freeze):
325+
# Default method uses liquid_fraction_ramp, should differ from pure phases
326+
T_mixed = FT(260.0)
327+
dq_dT_mixed_default = TD.∂q_vap_sat_∂T(param_set, T_mixed, ρ)
328+
dq_dT_mixed_liquid = TD.∂q_vap_sat_∂T(param_set, T_mixed, ρ, TD.Liquid())
329+
dq_dT_mixed_ice = TD.∂q_vap_sat_∂T(param_set, T_mixed, ρ, TD.Ice())
330+
331+
# In mixed phase, default should be between pure liquid and pure ice
332+
@test min(dq_dT_mixed_liquid, dq_dT_mixed_ice) <= dq_dT_mixed_default <=
333+
max(dq_dT_mixed_liquid, dq_dT_mixed_ice)
334+
end
298335
end

0 commit comments

Comments
 (0)