Hi Everyone,
I was recently working on writing regression test suite for the i.eb.hsebal01 module, and I have encountered persistent failures due to Delta T Convergence failed errors, even when providing seemingly valid and diverse synthetic input maps.
The purpose of this test is to simulate minimal but physically consistent input conditions that can:
trigger both manual and automatic pixel detection modes,
provide stable heat flux results for future validation,
and help ensure stability in CI environments.
Inputs Used
I am working in a small test region (10x10, LL projection). Inputs include:
netradiation: higher over vegetated pixels
soilheatflux: computed as 30% of Rn
temperaturemeansealevel: cooler in wet zones, hotter in dry
vapourpressureactual: higher for wet zones
aerodynresistance: inversely correlated with NDVI
frictionvelocitystar: ~0.25 (nominal)
Example NDVI generation:
r.mapcalc expression="ndvi = if(row()==5 && col()==5, 0.8, if(row()==9 && col()==9, 0.1, 0.5 + 0.05 * rand(0,1)))"
I have explicitly declared (5,5) as wet and (9,9) as dry pixels for the manual mode.
Problem
Despite a stable region and reasonable gradients between wet and dry zones, the module still fails with:
ERROR: Delta T Convergence failed, exiting prematurely, please check output
This happens both with -a (automatic detection) and with manually specified wet/dry coordinates.
What I have Tried
Aligning g.region to raster extents (e.g., g.region raster=ndvi)
Using float values vs integer
Adjusting synthetic values to stay within realistic SEBAL bounds
Setting row_wet_pixel, column_wet_pixel manually
Testing against both stable and dev builds of GRASS 8.4
If anyone has:
suggestions on stabilizing Delta T convergence with synthetic data,
or knowledge of thresholds/tolerances in the internal logic,
I would greatly appreciate any guidance. Ultimately, I would like to contribute this as a full regression test under imagery/i.eb.hsebal01/testsuite.
Thanks in advance!
jayneels:
i.eb.hsebal01
Unless the author @ychemin wants to comment, I suggest to move on a different tool.
ychemin
December 10, 2025, 8:41am
3
Hi, I was not online in discourse, can you send me a full script to try what you guys are doing. I will debug that.
I don’t remember where we ended with this, Jayneel fixed some things and wrote a testsuite:
opened 01:47PM - 01 Jul 25 UTC
closed 03:19PM - 02 Jul 25 UTC
bug
imagery
## Describe the bug
When using manual mode in `i.eb.hsebal01` (providing `row_we… t_pixel`, `col_wet_pixel`, `row_dry_pixel`, `col_dry_pixel`) without `-a` flag, the module fails with:
> ERROR: Delta T Convergence failed, exiting prematurily, please check output
## To reproduce
1. Define a simple region (example):
```
g.region n=10000 s=0 e=10000 w=0 res=1000
```
2. Create the necessary input maps:
```
r.mapcalc expression="netrad=500 + col()"
r.mapcalc expression="soilheat=50 + col()"
r.mapcalc expression="z0m=0.1"
r.mapcalc expression="temp=250 + row()*5 + col()*2"
r.mapcalc expression="vap=2 + row()/10.0"
```
3. Run `i.eb.hsebal01` without the `-a` flag:
```
i.eb.hsebal01 netradiation=netrad soilheatflux=soilheat aerodynresistance=z0m temperaturemeansealevel=temp vapourpressureactual=vap frictionvelocitystar=0.32407 row_wet_pixel=0 column_wet_pixel=0 row_dry_pixel=9 column_dry_pixel=9 output=hsebal_manual
```
4. Observe that the module fails.
## Screenshots

## System description
- Operating System: Windows Subsystem for Linux (WSL)
- GRASS GIS version: 8.4
- details about further software components
- GRASS 8.5.0dev
- Python verison: 3.10.12
- wxPython version: 4.2.2
## Root Cause Analysis
- The code (`main.c`) only loads `z0m`, `eact`, and `t0dem` rasters into the `d_*` arrays inside the initialization loop if auto mode is active (`-a` flag is active).
- In manual mode, the arrays remain uninitialized, so `d_z0m`, `d_eact` used to compute `d_Rah_dry` and `d_Roh_dry` contain all zeros.
- Additionally, the `rowWet`, `colWet`, `rowDry`, `colDry` indices are not set from `m_row_*` and `m_col_*` unless the `-c` flag is used.
## Working Fix
To fix this, I made three changes:
1. Explicitly assign row/col indices if not using `-c` flag:
Add these lines after line 211 in `main.c`.
```c
if (!flag3->answer) {
rowWet = (int)m_row_wet;
colWet = (int)m_col_wet;
rowDry = (int)m_row_dry;
colDry = (int)m_col_dry;
}
```
2. Always initialize rasters into `d_t0dem`, `d_z0m`, `d_eact` before further processing:
Add this code block after line 309 in `main.c`.
```c
/* Always read t0dem, z0m, and eact into the arrays */
for (row = 0; row < nrows; row++) {
Rast_get_d_row(infd_t0dem, inrast_t0dem, row);
Rast_get_d_row(infd_z0m, inrast_z0m, row);
Rast_get_d_row(infd_eact, inrast_eact, row);
for (col = 0; col < ncols; col++) {
d_t0dem[row][col] = ((DCELL *)inrast_t0dem)[col];
d_z0m[row][col] = ((DCELL *)inrast_z0m)[col];
d_eact[row][col] = ((DCELL *)inrast_eact)[col];
}
}
```
3. Cleaned up verbosity so `-c` flag messages only print if `flag3->answer` is true:
(Note: This was not related to the described bug, but was a missed indentation issue causing the messages to appear unconditionally.)
Modify lines 213,214, 215 and 216 in the existing code.
```c
/*If pixels locations are in projected coordinates */
if (flag3->answer) {
G_verbose_message(_("Manual wet/dry pixels in image coordinates"));
G_verbose_message(_("Wet Pixel=> x:%f y:%f"), m_col_wet, m_row_wet);
G_verbose_message(_("Dry Pixel=> x:%f y:%f"), m_col_dry, m_row_dry);
}
```
After applying these fixes, manual mode works identically to auto mode, producing correct sensible heat flux outputs with no convergence errors. I can prepare a patch making suggested changes to fix the issue ensuring consistent raster initialization and clear pixel indexing logic.
main ← jayneel-shah18:i_eb_hsebal01_fix
opened 03:40PM - 01 Jul 25 UTC
This PR fixes issues mentioned in #6003 that prevent the module from working pro… perly when manual wet/dry pixels are specified:
- **Missing initialization of raster arrays:**
- In manual mode, `d_t0dem`, `d_z0m`, and `d_eact` arrays were never populated before iteration loops. This caused all pixel values to remain zero, leading to invalid `d_rah_dry` and `d_roh_dry`, `NaN` results, and premature failure with `Delta T Convergence failed`.
- Fix: Added this loop after raster input buffers are allocated:
```c
/* Always read t0dem, z0m, and eact into the arrays */
for (row = 0; row < nrows; row++) {
Rast_get_d_row(infd_t0dem, inrast_t0dem, row);
Rast_get_d_row(infd_z0m, inrast_z0m, row);
Rast_get_d_row(infd_eact, inrast_eact, row);
for (col = 0; col < ncols; col++) {
d_t0dem[row][col] = ((DCELL *)inrast_t0dem)[col];
d_z0m[row][col] = ((DCELL *)inrast_z0m)[col];
d_eact[row][col] = ((DCELL *)inrast_eact)[col];
}
}
```
- **Row/column assignment for manual coordinates:**
- When using manual mode *without* the `-c` flag, the provided wet/dry pixel coordinates must be interpreted as row/col indices. These were not assigned properly to `rowWet`, `colWet`, `rowDry`, `colDry`, leading to uninitialized or stale values.
- Fix: Added this logic:
```c
/* If -c flag is not set, interpret inputs as integer row/col indices */
if (!flag3->answer) {
rowWet = (int)m_row_wet;
colWet = (int)m_col_wet;
rowDry = (int)m_row_dry;
colDry = (int)m_col_dry;
}
```
- **Verbosity cleanup:**
- The verbosity messages for projection coordinates were displayed regardless of whether `-c` was specified, causing confusion.
- Fix: Indented the messages so they only print if `flag3->answer` is true:
```c
/* If pixels locations are in projected coordinates */
if (flag3->answer) {
G_verbose_message(_("Manual wet/dry pixels in image coordinates"));
G_verbose_message(_("Wet Pixel=> x:%f y:%f"), m_col_wet, m_row_wet);
G_verbose_message(_("Dry Pixel=> x:%f y:%f"), m_col_dry, m_row_dry);
}
```
The changes in the module have been verified to run successfully in all modes:
* Automatic pixel selection (`-a`)
* Manual row/col pixel input (without `-c`)
* Manual projected coordinates (with `-c`)
The results matched the expected output rasters in each mode.
A test suite for the module will be added once these changes are merged.
Closes #6003
main ← jayneel-shah18:i_eb_hsebal01_tests
opened 01:08PM - 06 Jul 25 UTC
This PR introduces a regression and property-based test suite for the `i.eb.hseb… al01` module in GRASS. The tests are designed to validate the correctness and reproducibility of sensible heat flux computation under various input scenarios and to enforce key theoretical constraints derived from SEBAL methodology.
### Tests Included
- **`test_hsebal01_auto_pixels`**: Verifies that automatic wet/dry pixel detection (`-a` flag) produces consistent output statistics on raster inputs, confirming the reproducibility of the default workflow.
- **`test_hsebal01_manual_pixels_rowcol`**: Checks that specifying wet/dry pixels using row and column indices yields stable, predictable results identical to expectations.
- **`test_hsebal01_manual_pixels_coordinates`**: Tests correct handling of coordinate-based pixel specification (`-c` flag), ensuring coordinate inputs are interpreted accurately.
- **`test_hsebal01_with_different_friction_velocity`**: Validates that changing the friction velocity parameter affects output, confirming that this input is used correctly in the model.
- **`test_hsebal01_energy_balance_constraint`**: Asserts that the computed sensible heat flux does not exceed the available energy (`Rn - G`), enforcing theoretical energy conservation.
- **`test_hsebal01_monotonicity_along_temperature`**: Confirms that higher input temperatures correspond to higher mean sensible heat flux, validating expected monotonicity trends in output.
Looking forward to feedback and improvements.
I am not sure we resolved the problem described here. Jayneel won’t be working on this anymore.