Skip to content

fix(thermo): Apply temperature-dependent latent heat of vaporisation (and sublimation) everywhere in ED-MORE - #49

Merged
xiangtaoxu merged 4 commits into
EDmodel:betafrom
mpaiao:fix/latent-heat-vap-temperature
Nov 3, 2026
Merged

xiangtaoxu merged 4 commits into
EDmodel:betafrom
mpaiao:fix/latent-heat-vap-temperature

Conversation

@mpaiao

@mpaiao mpaiao commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

What and why

ED-MORE mostly followed ED2 in how energy exchanges were computing. However, when computing the latent heat fluxes, the code was using the latent heat of vaporisation at 0°C. This is actually inconsistent with the definition of enthalpy across the code, which requires latent heat of vaporisation (or sublimation) to be a function of temperature. This PR addresses this.

For reference:

  • Vaporisation (liquid to vapour): Lv = Lv(T3) + ( cp,vap - cp,liq ) × (T-T3)
  • Sublimation (ice to vapour): Ls = Lfusion(T3) + Lv(T3) + ( cp,vap - cp,liq ) × (T-T3)
  • If both occur, the latent heat is scaled by the liquid water fraction.

To reduce ambiguity, latent heat of vaporisation at the triple point is now named latent_heat_vap_3ple, and latent_heat_vap is now a function.

A direct consequence on the code is that evapotranspiration is now computed directly from water flux exchanges, instead of using latent heat to do so, because they can no longer be related by a constant. Because of this, both evapotranspiration and latent heat flux can be recorded in the output.

A few minor tweaks:

  • The row index PA_TRANSP is now PA_ET. The index was already referring to evapotranspiration, so I asked Claude to update the name and make it consistent.
  • The tests for energy now use the temperature function (latent_heat_vap) instead of constants (latent_heat_vap_3ple)

Effect on results (tick exactly one; CI checks)

  • Bit-for-bit: output is identical to the base commit's in every run listed under
    Verification. A new option that is off by default counts when the model is unchanged with it off.
  • Round-off: differences at round-off level only, e.g. from reordered sums.
  • Answer-changing: results move; give the largest changes below.

This only affects diagnostic outputs (mean_le_pa and mean_le_py) and the sub-step evapotranspiration, not the actual energy and water states. But this is a desirable change, which makes the output consistent with the code thermodynamics.

| Run (site or example) | Variable | Period | Before | After |
| region test, all cases | mean_le_pa (mean; largest daily change 3.17 W/m2) | Jan-Feb 2021 | 14.91 W/m2 | 16.85 W/m2 (+13.0 %) |
| region test, all cases | mean_le_py (mean; largest daily change 3.17 W/m2) | Jan-Feb 2021 | 14.69 W/m2 | 16.60 W/m2 (+12.9 %) |
| region test, all cases | mean_et_py | Jan-Feb 2021 | | round-off only (6 of 263 records) |

The canopy air is mostly below freezing in this run, so the vapour takes the heat of sublimation (2.835e6 J/kg, 13 % above the old constant). This is a desirable change as the latent heat of sublimation is indeed higher than latent heat of vaporisation. Evapotranspiration and the model state are unchanged.

User-facing changes (tick all that apply; each needs an "Upgrading" note in the changelog)

  • None
  • Config keys added, renamed or removed, or a value in the shipped config files changed
  • Output variables or output files changed
  • Input files (forcing, maps, initial conditions) or their format changed
  • Building or running changed: command-line or CMake options, dependencies, the Python API
  • State files written by the last release no longer load

Output variables mean_le_py and mean_le_pa will change values, but not their units of their meaning. New variable mean_et_pa included, which reports patch-level evapotranspiration in kg m−2 s−1.

Verification

  • Base commit (beta): 2bf6216
  • Runs compared with the base commit, and the result: region test daily output, 168 files. Of 148
    variables, 3 change (the table above); 145 are identical. gfortran only.
  • The long tests (label long; expected for a science change): not run (none are registered in
    the dev build)
  • Other builds (production preset, nvfortran): not run; ifx not run locally (CI covers it)
  • Conservation warnings in those runs, or "none": not checked beyond the region test's area check
  • Full ctest suite (88 tests), the lint checks and the Python tests (105): passed, gfortran dev build.

Credit and AI use

  • Collaborators:
  • AI assistance (the tool and what it did), or "none": Claude Code (Sonnet 5.5) wrote the function, the call-site changes, the tests and the docs, and ran the gfortran suite. The commits carry the Co-Authored-By trailer.
  • I answer for this pull request: I can explain every change in it, and the verification above was actually run

Not in this PR

There is a follow-up PR #51 that depends on this PR. That one rectifies the saturation vapour pressure to be consistent with the remaining thermodynamics, similar to ED2's pull request 445.

Another minor edit will be the calculation of a few constants, to ensure that they are accurate and consistent (so physical dependencies between constants are written explicitly).

From Claude: The ice share is approximate. The pack's vapour enters the canopy air before the export, so the share of the export is not exact.

Based on a draft generated with Claude Code

@xiangtaoxu

xiangtaoxu commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Thanks, Marcos! I am planning to have several PRs to fix bugs in output and improve output organization this week. I see your PR has several changes already. Is it ok to merge this science update after the output PRs?

e.g. I also noticed the PA_TRANSP is actually ET

@mpaiao

mpaiao commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Yes, I will make a few minor science PRs now just because I don't want to forget, but feel free to push the output PRs first. I am also still trying to figure out how to run ED-MORE, so it will take me a while to get the test runs going (hence the draft).

@xiangtaoxu

Copy link
Copy Markdown
Contributor

@mpaiao , I merged a few output-specific PRs yesterday (no change in science). Could you update your PRs with the latest beta branch? Then I will review them and merge them sequentially

@xiangtaoxu

Copy link
Copy Markdown
Contributor

Oh, and I forgot to mention, please add your name to the AUTHORS (

Xiangtao Xu
) and CITATION (

ED-MORE/CITATION.cff

Lines 7 to 10 in c6f7e2f

authors:
- family-names: Xu
given-names: Xiangtao
affiliation: Cornell University
) in one your PR.

@mpaiao

mpaiao commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Sure, I added myself to the AUTHORS, and I will rebase the pull request.

mpaiao and others added 3 commits October 30, 2026 12:10
…blimation

latent_heat_vap(t_k, fliq) in ed_therm_lib is Kirchhoff's law on the
energy datum: liquid vaporizes with latent_heat_vap_3ple + (cp_vap -
cp_liq) (T - t_3ple), supercooled or not, ice sublimes with
latent_heat_fusion + latent_heat_vap_3ple + (cp_vap - cp_ice)
(T - t_3ple), and a mixture takes their average weighted by fliq. The
constant is renamed latent_heat_vap_3ple, its value at t_3ple, as in ED2.

The step books its latent heat once (step_exports_t%atm_le_export): the
vapour exported at its latent heat at the final canopy-air temperature,
the snow pack's share as ice and the rest as liquid. The reported LE and
the energy budget's outflows read that number, so the output budget
still closes. The plant-energy tests build their pathways with
latent_heat_vap(T_CAS, 1).

Effect on results: the state is bit-for-bit unchanged; the reported LE
and the budget's outflow split change.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… updated beta

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@mpaiao
mpaiao force-pushed the fix/latent-heat-vap-temperature branch from 3d1a6af to 5bdf360 Compare November 2, 2026 10:53
Comment thread CHANGELOG-v0.md Outdated
`mean_veg_surface_evap_pa`, `mean_ground_evap_pa`, `mean_dew_pa`, `mean_resid_energy_pa` and
`mean_resid_water_pa`. (#60)

### Answer changes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a minor stylistic comment to follow the guideline in CONTRIBUTING. Can you Move The Answer changes section before 'the Added' section?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure, I will do it.

Comment thread CHANGELOG-v0.md Outdated

### Answer changes

- The latent heat flux (`mean_le_pa`, `mean_le_py`) rises by 13 % on average (January 2021: 16.6 to

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I did a quick run for example 04. At the warm BCI site, LE declined by ~2.5% (66.87 W/m2 to 65.12 W/m2 for calibrated runs). Maybe it is worth adding it here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, I can add it.

!--- Net CAS -> atmosphere export over the step, b-weighted like the ledgers: the reported H, LE.
real(wp) :: atm_heat_export = 0.0_wp ! [J/m2] sensible heat over this dt_fast
real(wp) :: atm_vap_export = 0.0_wp ! [kg/m2] over this dt_fast
real(wp) :: atm_le_export = 0.0_wp ! [J/m2] latent heat of that vapour, at its latent heat (reported LE * dt_fast)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

there are two "latent heat" in the comment. I guess the second one should be temperature?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think Claude made this comment, I will fix it.

@xiangtaoxu xiangtaoxu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, Marcos! I ran the PR, which seems all fine. I left some minor stylistic comments. In addition, could you add your name to CITATION.cff as well? Currently it is not automatically tracking AUTHORS.... Once you do these minor changes, I will merge the PR.

@mpaiao
mpaiao force-pushed the fix/latent-heat-vap-temperature branch from b5aaf3c to 338e1bf Compare November 3, 2026 11:08
@mpaiao
mpaiao force-pushed the fix/latent-heat-vap-temperature branch from 338e1bf to db36a99 Compare November 3, 2026 11:10
@mpaiao

mpaiao commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

@xiangtaoxu I think I addressed all the changes, please take a look and see if it is all good.

Note that the comment on temperature dependency was dropped. I had rewritten it, but the line became too long (> 132 characters). I asked Claude to trim it and Claude dropped the temperature dependence bit. This was the prompt I got from Claude:

I shortened the trailing comment on line 325 of ed_fast_integ_types.f90. It now reads ! [J/m2] latent heat over this dt_fast (reported LE * dt_fast). I dropped "temperature-dependent" because the field's name and use already say it. No line in that file is over 132 columns now.

I don't think it is indeed necessary to point out that latent heat depends on temperature, that comment is just to say what the variable stores (latent heat).

@xiangtaoxu
xiangtaoxu merged commit dc685f9 into EDmodel:beta Nov 3, 2026
4 checks passed
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.

2 participants