Skip to content

Commit b04a5df

Browse files
committed
Cleanup
1 parent e7c2e20 commit b04a5df

File tree

8 files changed

+88
-29
lines changed

8 files changed

+88
-29
lines changed

docs/src/API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,12 @@ air_pressure_given_θ
112112
```@docs
113113
saturation_vapor_pressure
114114
q_vap_saturation
115+
∂q_vap_sat_∂T
115116
supersaturation
116117
saturation_excess
117118
q_vap_from_RH
118119
q_vap_from_p_vap
119120
q_vap_saturation_from_pressure
120-
∂q_vap_sat_∂T
121121
```
122122

123123
### Saturation Adjustment

src/air_energies.jl

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -282,21 +282,32 @@ The total energy is `e_tot = e_int + e_kin + e_pot`.
282282
end
283283

284284
"""
285-
total_enthalpy(e_tot, R_m, T)
285+
total_enthalpy(param_set, e_tot, T, q_tot=0, q_liq=0, q_ice=0)
286286
287-
The total specific enthalpy.
287+
The total enthalpy per unit mass, `h_tot = e_tot + R_m T`.
288288
289289
# Arguments
290-
- `e_tot`: total specific energy [J/kg]
291-
- `R_m`: gas constant of moist air [J/(kg·K)], see [`gas_constant_air`](@ref)
292-
- `T`: air temperature [K]
290+
- `param_set`: thermodynamics parameter set, see [`Thermodynamics`](@ref)
291+
- `e_tot`: total specific energy [J/kg], see [`total_energy`](@ref)
292+
- `T`: temperature [K]
293+
- `q_tot`: total specific humidity [kg/kg]
294+
- `q_liq`: liquid specific humidity [kg/kg]
295+
- `q_ice`: ice specific humidity [kg/kg]
293296
294297
# Returns
295298
- `h_tot`: total specific enthalpy [J/kg]
296299
297-
The total enthalpy is computed as `h_tot = e_tot + R_m T`.
300+
In the dry limit (`q_tot = q_liq = q_ice = 0`, the default), this reduces to the dry-air expression.
298301
"""
299-
@inline function total_enthalpy(e_tot, R_m, T)
302+
@inline function total_enthalpy(
303+
param_set::APS,
304+
e_tot,
305+
T,
306+
q_tot = 0,
307+
q_liq = 0,
308+
q_ice = 0,
309+
)
310+
R_m = gas_constant_air(param_set, q_tot, q_liq, q_ice)
300311
return e_tot + R_m * T
301312
end
302313

src/air_saturation_functions.jl

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -388,11 +388,21 @@ Derivative of saturation vapor specific humidity with respect to temperature.
388388
- `phase`: (Optional) Phase (Liquid() or Ice()).
389389
390390
# Returns
391-
- `∂q_v^* / ∂T`: Derivative of saturation specific humidity with respect to temperature [kg/kg/K].
392-
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.
391+
- `∂q_vap_sat / ∂T`: Derivative of saturation specific humidity with respect to temperature [kg/kg/K].
392+
393+
Computed as
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 and the ideal gas law,
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/depr_phase_partition_methods.jl

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -583,28 +583,22 @@ and total specific humidity.
583583
end
584584

585585
"""
586-
total_enthalpy(param_set, e_tot, T[, q::PhasePartition])
586+
total_enthalpy(param_set, e_tot, T, q::PhasePartition)
587587
588588
The total specific enthalpy, defined as `e_tot + R_m * T`.
589589
590590
- `param_set` a thermodynamics parameter set, see the [`Thermodynamics`](@ref) for more details
591591
- `e_tot` total specific energy
592592
- `T` temperature
593-
594-
and, optionally,
595-
596593
- `q` [`PhasePartition`](@ref).
597-
598-
When `q` is not provided, the results are for dry air.
599594
"""
600595
@inline function total_enthalpy(
601596
param_set::APS,
602597
e_tot,
603598
T,
604-
q::PhasePartition = q_pt_0(param_set),
599+
q::PhasePartition,
605600
)
606-
R_m = gas_constant_air(param_set, q)
607-
return total_enthalpy(e_tot, R_m, T)
601+
return total_enthalpy(param_set, e_tot, T, q.tot, q.liq, q.ice)
608602
end
609603

610604
# -------------------------------------------------------------------------

src/depr_state_methods.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ The total specific enthalpy.
326326
@inline function total_enthalpy(param_set::APS, ts::ThermodynamicState, e_tot)
327327
R_m = gas_constant_air(param_set, ts)
328328
T = air_temperature(param_set, ts)
329-
return total_enthalpy(e_tot, R_m, T)
329+
return e_tot + R_m * T
330330
end
331331

332332
"""

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

test/correctness.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,13 @@ using the non-deprecated functional API (no `PhasePartition`/state types).
261261
s_vd =
262262
TD.virtual_dry_static_energy(param_set, T, e_pot, q_tot, q_liq, q_ice)
263263
@test s_vd cp_d * (T_virt - T_0) + e_pot
264+
265+
# Total enthalpy: h_tot = e_tot + R_m * T
266+
e_kin = FT(50) # Kinetic energy [J/kg]
267+
e_tot = TD.total_energy(param_set, e_kin, e_pot, T, q_tot, q_liq, q_ice)
268+
h_tot = TD.total_enthalpy(param_set, e_tot, T, q_tot, q_liq, q_ice)
269+
R_m = TD.gas_constant_air(param_set, q_tot, q_liq, q_ice)
270+
@test h_tot e_tot + R_m * T
264271
end
265272

266273
@testset "Humidity definitions (\$FT)" begin

0 commit comments

Comments
 (0)