Skip to content

Conversation

@BenjaminTJohnson
Copy link
Contributor

@BenjaminTJohnson BenjaminTJohnson commented Jan 12, 2026

Motivation

  • LandEM component of CRTM only exposes LAI as a vegetation state input; there is no explicit canopy‑water parameter in the forward API.
  • To capture canopy‑water sensitivity consistently, we represent canopy water content as an additive contribution to LAI, i.e. effective LAI = LAI + Canopy_Water_Content. This keeps the forward model unchanged while
    enabling canopy‑water influence through the same vegetation optics pathway.

Decision process: analytic TL/AD/K_Matrix

  • CRTM requires coded TL/AD/K_Matrix (except for verification)
  • The existing LandEM forward model had no TL/AD hooks, so the framework had nothing to call for LAI/canopy sensitivities.
  • We therefore derive an analytic LAI derivative by following the same canopy optics + pathway used in the forward model, then propagate that derivative through TL/AD/K_Matrix.
  • Finite‑difference checks are retained only as verification in unit tests.

What changed (relative to develop)

Core model

  • Added an analytic derivative helper NESDIS_LandEM_LAI_Derivative that computes ∂(emissivity)/∂(LAI) through canopy optics and the two‑stream solution.
  • Canopy_Optic now optionally returns internal transmittance terms (tv, th) needed to compute d(tau)/d(LAI).
  • Land surface TL/AD now uses the analytic derivative and maps sensitivities to both LAI and canopy water via LAI + Canopy_Water_Content.
    • TL: dE/dLAI * (LAI_TL + Canopy_Water_TL)
    • AD: accumulates into Surface_AD%Lai and Surface_AD%Canopy_Water_Content

Unit tests

  • New LAI tests:
    • TL finite‑difference consistency: test_TL_lai.f90
    • K‑matrix finite‑difference consistency: test_k_matrix_lai.f90
  • New AD dot‑product test:
    • test_AD_lai_canopy_water.f90 validates TL/AD consistency for the combined LAI + canopy water perturbation.
  • Canopy‑water TL/K tests now take sensor ID via argv (same as existing ctests) and set a stable LAI baseline for consistent sensitivities.

CMake test structure

  • Refactored land surface unit tests into list‑driven loops (test types × sensor IDs), matching the generic style used elsewhere in test/CMakeLists.txt.

Files touched

  • src/SfcOptics/NESDIS_Emissivity/NESDIS_LandEM_Module.f90
  • src/SfcOptics/CRTM_MW_Land_SfcOptics.f90
  • test/CMakeLists.txt
  • test/mains/unit/Unit_Test/test_TL_canopy_water.f90
  • test/mains/unit/Unit_Test/test_k_matrix_canopy_water.f90
  • test/mains/unit/Unit_Test/test_TL_lai.f90 (new)
  • test/mains/unit/Unit_Test/test_k_matrix_lai.f90 (new)
  • test/mains/unit/Unit_Test/test_AD_lai_canopy_water.f90 (new)

Notes / behavior

  • Sensitivity is disabled when LAI + Canopy_Water_Content <= 0, matching the forward model’s effective LAI guard.
  • Emissivity clamp behavior in LandEM is preserved; derivative output is zeroed when emissivity is clamped by the forward limits.

Tests

ctest -R test_Unit_TL_Canopy_Water_
ctest -R test_Unit_TL_LAI_
ctest -R test_Unit_K_Matrix_Canopy_Water_
ctest -R test_Unit_K_Matrix_LAI_
ctest -R test_Unit_AD_LAI_Canopy_Water_

feel free to add other sensors to the test/CMakeLists.txt

list( APPEND Land_Surface_Sensor_Ids
        atms_n21
)

resolves #279 (at least mechanically).

Copy link
Contributor

@StegmannJCSDA StegmannJCSDA left a comment

Choose a reason for hiding this comment

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

The branch builds on Discover and all relevant ctests pass.
I have a few questions/comments on the build:

  • Is the TL and AD code coming from tapenade?
  • I don't understand the reasoning behind the effective LAI = LAI + Canopy_Water_Content

LAI and canopy water content have different units, so I wouldn't add them.
Personally I would use the canopy water content for the computation of the dielectric constant in the subroutine Canopy_Diel instead. The subroutine implements Eq. (10) from the attached paper (the reference is not given in the code documentation).
Microwave_Dielectric_Spectrum_of_Vegetation_-_Part_II_Dual-Dispersion_Model.pdf

@BenjaminTJohnson
Copy link
Contributor Author

You raise a valid point about the dimensional inconsistency. Let me explain the reasoning behind the current implementation.

Why the optical depth pathway rather than dielectric? The alternative approach (routing canopy water content through Canopy_Diel) would require implementing tangent-linear and adjoint code through the complex-valued dielectric calculations. Specifically:

  1. Canopy_Diel computes a complex dielectric constant eveg from gravimetric moisture content
  2. eveg feeds into Canopy_Optic where it affects leaf reflectance/transmittance (rv, rh, tv, th) through complex exponentials and Fresnel-type equations
  3. These in turn affect single-scattering albedo and optical depth
  4. Finally flows through the solver, etc.

Deriving and implementing ∂emissivity/∂CWC through this chain would require:

  • Defining the physical relationship between CWC (g/cm³) and gravimetric moisture mg
  • Complex-valued derivatives through Canopy_Diel
  • Propagating those through the Canopy_Optic calculations

This is substantially more complex than the optical depth pathway, where the relationship is linear and real-valued.

Physical basis for the current approach: Both LAI and canopy water content affect the canopy's microwave opacity, more vegetation mass (whether leaf area or water content) means more attenuation. The optical depth in Canopy_Optic is linear in LAI:tau = 0.5 * vlai * (2 - tv - th). I was struggling with the observation that LAI is what supports CWC; a zero LAI implies zero CWC, because there's no leaf to hold the water, and was trying to find a solution that accommodates this.

Treating canopy water as an additional contribution to this opacity is a reasonable first-order approximation for enabling Jacobian calculations.

The dimensional inconsistency is a fair criticism. We could address this by adding an explicit conversion coefficient:

  REAL(fp), PARAMETER :: CWC_TO_LAI_COEFF = 1.0_fp  ! [m²/m² per g/cm³]
  lai_eff = Surface%Lai + Surface%Canopy_Water_Content * CWC_TO_LAI_COEFF

This makes the unit conversion explicit and auditable, and the coefficient could be refined based on empirical calibration or made vegetation-type dependent.

Would this address your concern, or do you feel the dielectric pathway is essential for physical correctness?

@StegmannJCSDA
Copy link
Contributor

Hello @BenjaminTJohnson. Thank you for the comprehensive explanation.
Yes, the conversion coefficient implementation is probably the best solution:

 REAL(fp), PARAMETER :: CWC_TO_LAI_COEFF = 1.0_fp  ! [m²/m² per g/cm³]
 lai_eff = Surface%Lai + Surface%Canopy_Water_Content * CWC_TO_LAI_COEFF

The complexity of the dielectric pathway might introduce new problems on top of everything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CRTM CRTM

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Canopy_Water_Content input is unused

2 participants