Skip to content

heat_index warnings and bug#807

Merged
anissa111 merged 15 commits intoNCAR:mainfrom
anissa111:negsqrt
Jan 15, 2026
Merged

heat_index warnings and bug#807
anissa111 merged 15 commits intoNCAR:mainfrom
anissa111:negsqrt

Conversation

@anissa111
Copy link
Member

@anissa111 anissa111 commented Jan 13, 2026

PR Summary

This PR rewrites heat_index to match the NWS's javascript implementation on https://www.wpc.ncep.noaa.gov/html/heatindex.shtml

function heat1_c(HI1_C){
    if(HI1_C.RHumidity.value > 100){
        alert("Relative humidity cannot exceed 100%.");
    }
    else if (HI1_C.RHumidity.value < 0) {
        alert("Relative humidity cannot be less than 0%.");
    }
    else if (parseFloat(HI1_C.tempair.value) <= 4.44) {
        var hi = HI1_C.tempair.value;
    }
    else{
        var tempair_in_fahrenheit = 1.80 * HI1_C.tempair.value + 32.0;
        var hitemp = 61.0+((tempair_in_fahrenheit-68.0)*1.2)+(HI1_C.RHumidity.value*0.094);
        var fptemp = parseFloat(HI1_C.tempair.value);
        var hifinal = 0.5*(fptemp+hitemp);

        if(hifinal > 79.0){
            var hi = -42.379 + 2.04901523 * tempair_in_fahrenheit + 10.14333127 * HI1_C.RHumidity.value - 0.22475541 * tempair_in_fahrenheit * HI1_C.RHumidity.value - 6.83783 * (Math.pow(10, -3)) * (Math.pow(tempair_in_fahrenheit, 2)) - 5.481717 * (Math.pow(10, -2)) * (Math.pow(HI1_C.RHumidity.value, 2)) + 1.22874 * (Math.pow(10, -3)) * (Math.pow(tempair_in_fahrenheit, 2)) * HI1_C.RHumidity.value + 8.5282 * (Math.pow(10, -4)) * tempair_in_fahrenheit * (Math.pow(HI1_C.RHumidity.value, 2)) - 1.99 * (Math.pow(10, -6)) * (Math.pow(tempair_in_fahrenheit, 2)) * (Math.pow(HI1_C.RHumidity.value,2));

            if((HI1_C.RHumidity.value <= 13) && (tempair_in_fahrenheit >= 80.0) && (tempair_in_fahrenheit <= 112.0)) {
                var adj1 = (13.0-HI1_C.RHumidity.value)/4.0;
                var adj2 = Math.sqrt((17.0-Math.abs(tempair_in_fahrenheit-95.0))/17.0);
                var adj = adj1 * adj2;
                var hi = hi - adj;
            }
            else if ((HI1_C.RHumidity.value > 85.0) && (tempair_in_fahrenheit >= 80.0) && (tempair_in_fahrenheit <= 87.0)) {
                var adj1 = (HI1_C.RHumidity.value-85.0)/10.0;
                var adj2 = (87.0-tempair_in_fahrenheit)/5.0;
                var adj = adj1 * adj2;
                var hi = hi + adj;
            }
        }
        else{
            var hi = hifinal;
        }
    }
    twoplaces_rh1 = hi.toFixed(1);
    celsius_temp_rh1 = (hi - 32) * .556;
    twoplaces_c_rh1 = celsius_temp_rh1.toFixed(1);
    HI1_C.heatindex.value = twoplaces_rh1 + " F" +  " / " + twoplaces_c_rh1 + " C";

Summary of changes:

  • updates steadman calculation to avoid duplication of the temperature average
  • changes some of the ranges very slightly where adjustments are applied to match javascript implementation
  • removes unnecessary internal functions _xheat_index and _heat_index and absorbed them into the main heat_index function
  • slightly alters the xarray metadata to reflect the calculation changes
  • fixes heat_index encounters sqrt of negatives #596 by adding an abs to the sqrt calculation
  • adds testing based on output from the NWS javascript

In case anybody needs to do this in the future, this is the script I ran on a chrome console on the webpage with the javascript:

const form = document.forms[4];
const t = form.elements[0];
const rh = form.elements[4];
const calc = form.elements[5];
const out = form.elements[7];
const rhs = [40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100];
const temps = [80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110];

var nt = temps.length;
var nrh = rhs.length;

for (let i = 0; i < nt; i++) {
    for (let j = 0; j < nrh; j++) {
        t.value = temps[i];
        rh.value = rhs[j];
        calc.click();
        result[i][j] = out.value.split(' ')[0];
        console.log(temps[i] + " " + rhs[j] + " " + result[i][j])
    }
}

// make csv
let csvContent = ''

result.forEach(row => {
  csvContent += row.join(',') + '\n'
})

const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8,' })
const objUrl = URL.createObjectURL(blob)

const link = document.createElement('a')
link.setAttribute('href', objUrl)
link.setAttribute('download', 'File.csv')
link.textContent = 'Click to Download'

document.querySelector('body').append(link)

link //click on link displayed

Related Tickets & Documents

Closes #806 and #596

PR Checklist

General

  • PR includes a summary of changes
  • Link relevant issues, make one if none exist
  • Add a brief summary of changes to docs/release-notes.rst in a relevant section for the upcoming release.
  • Add appropriate labels to this PR
  • PR follows the Contributor's Guide

Functionality

  • New function(s) intended for public API added to geocat/comp/__init__.py file

Testing

  • Update or create tests in appropriate test file

@anissa111 anissa111 added bug Something isn't working testing Issue related to testing labels Jan 13, 2026
@anissa111 anissa111 added the run-benchmark Add tag to a PR to run ASV comparison on new commits label Jan 13, 2026
@github-actions
Copy link

github-actions bot commented Jan 13, 2026

Meowdy! Here's your benchmark comparison:
Baseline: 004f90d (NCAR:main)
Contender: dcd4cab (anissa111:negsqrt)

Benchmarks that have stayed the same:

Change Before [004f90d] After [dcd4cab] Ratio Benchmark (Parameter)
426±3ms 430±2ms 1.01 bench_climatologies.Bench_calendar_average.time_calendar_average [runnervmi13qx/conda-py3.10]
427±10ms 428±2ms 1 bench_climatologies.Bench_calendar_average.time_calendar_average [runnervmi13qx/conda-py3.13]
37.6±0.4ms 39.2±0.2ms 1.04 bench_climatologies.Bench_climate_anomaly.time_climate_anomaly [runnervmi13qx/conda-py3.10]
37.5±0.6ms 38.6±0.3ms 1.03 bench_climatologies.Bench_climate_anomaly.time_climate_anomaly [runnervmi13qx/conda-py3.13]
44.9±0.7ms 45.0±1ms 1 bench_climatologies.Bench_climatology_average.time_climatology_average [runnervmi13qx/conda-py3.10]
45.2±0.4ms 44.3±0.3ms 0.98 bench_climatologies.Bench_climatology_average.time_climatology_average [runnervmi13qx/conda-py3.13]
7.94±0.06ms 7.92±0.06ms 1 bench_climatologies.Bench_month_to_season.time_month_to_season [runnervmi13qx/conda-py3.10]
8.33±0.2ms 8.01±0.03ms 0.96 bench_climatologies.Bench_month_to_season.time_month_to_season [runnervmi13qx/conda-py3.13]
510M 510M 1 bench_fourier.Bench_fourier.peakmem_band_block [runnervmi13qx/conda-py3.10]
510M 510M 1 bench_fourier.Bench_fourier.peakmem_band_block [runnervmi13qx/conda-py3.13]
145±1ms 143±1ms 0.99 bench_fourier.Bench_fourier.time_band_block [runnervmi13qx/conda-py3.10]
145±2ms 141±0.8ms 0.98 bench_fourier.Bench_fourier.time_band_block [runnervmi13qx/conda-py3.13]
262M 263M 1 bench_interpolation.Bench_interp_hybrid_to_pressure.peakmem_interp_hybrid_to_pressure [runnervmi13qx/conda-py3.10]
263M 263M 1 bench_interpolation.Bench_interp_hybrid_to_pressure.peakmem_interp_hybrid_to_pressure [runnervmi13qx/conda-py3.13]
39.4±0.4ms 39.7±0.3ms 1.01 bench_interpolation.Bench_interp_hybrid_to_pressure.time_interp_hybrid_to_pressure [runnervmi13qx/conda-py3.10]
39.9±0.5ms 39.5±0.2ms 0.99 bench_interpolation.Bench_interp_hybrid_to_pressure.time_interp_hybrid_to_pressure [runnervmi13qx/conda-py3.13]
258M 258M 1 bench_interpolation.Bench_interp_hybrid_to_pressure_extrap_temp.peakmem_interp_hybrid_to_pressure_extrap_temp [runnervmi13qx/conda-py3.10]
258M 258M 1 bench_interpolation.Bench_interp_hybrid_to_pressure_extrap_temp.peakmem_interp_hybrid_to_pressure_extrap_temp [runnervmi13qx/conda-py3.13]
18.8±0.2ms 18.6±0.2ms 0.99 bench_interpolation.Bench_interp_hybrid_to_pressure_extrap_temp.time_interp_hybrid_to_pressure_extrap_temp [runnervmi13qx/conda-py3.10]
18.5±0.2ms 18.4±0.1ms 1 bench_interpolation.Bench_interp_hybrid_to_pressure_extrap_temp.time_interp_hybrid_to_pressure_extrap_temp [runnervmi13qx/conda-py3.13]
719M 719M 1 bench_interpolation.Bench_interp_multidim.peakmem_interp_multidim_chunk [runnervmi13qx/conda-py3.10]
719M 719M 1 bench_interpolation.Bench_interp_multidim.peakmem_interp_multidim_chunk [runnervmi13qx/conda-py3.13]
5.18±0.06s 5.42±0.02s 1.05 bench_interpolation.Bench_interp_multidim.time_interp_multidim_chunk [runnervmi13qx/conda-py3.10]
5.39±0.01s 5.30±0.02s 0.98 bench_interpolation.Bench_interp_multidim.time_interp_multidim_chunk [runnervmi13qx/conda-py3.13]
3.38±0.03ms 3.42±0.04ms 1.01 bench_interpolation.Bench_interp_sigma_to_hybrid.time_interp_sigma_to_hybrid_1d [runnervmi13qx/conda-py3.10]
3.34±0.04ms 3.32±0.01ms 0.99 bench_interpolation.Bench_interp_sigma_to_hybrid.time_interp_sigma_to_hybrid_1d [runnervmi13qx/conda-py3.13]
14.7±0.2ms 14.4±0.3ms 0.98 bench_interpolation.Bench_interp_sigma_to_hybrid.time_interp_sigma_to_hybrid_3d [runnervmi13qx/conda-py3.10]
14.3±0.2ms 14.3±0.1ms 1 bench_interpolation.Bench_interp_sigma_to_hybrid.time_interp_sigma_to_hybrid_3d [runnervmi13qx/conda-py3.13]
8.92±0.02μs 8.74±0.1μs 0.98 bench_meteorology.Bench_actual_saturation_vapor_pressure.time_actual_saturation_vapor_pressure [runnervmi13qx/conda-py3.10]
8.79±0.1μs 8.60±0.03μs 0.98 bench_meteorology.Bench_actual_saturation_vapor_pressure.time_actual_saturation_vapor_pressure [runnervmi13qx/conda-py3.13]
18.6±0.2μs 18.4±0.5μs 0.99 bench_meteorology.Bench_delta_pressure.time_delta_pressure [runnervmi13qx/conda-py3.10]
18.4±0.07μs 18.0±0.06μs 0.98 bench_meteorology.Bench_delta_pressure.time_delta_pressure [runnervmi13qx/conda-py3.13]
17.1±0.2μs 17.3±0.03μs 1.01 bench_meteorology.Bench_dewtemp.time_dewtemp [runnervmi13qx/conda-py3.10]
17.2±0.1μs 17.3±0.1μs 1.01 bench_meteorology.Bench_dewtemp.time_dewtemp [runnervmi13qx/conda-py3.13]
183±0.8μs 179±2μs 0.98 bench_meteorology.Bench_heat_index.time_heat_index [runnervmi13qx/conda-py3.10]
181±1μs 176±0.7μs 0.97 bench_meteorology.Bench_heat_index.time_heat_index [runnervmi13qx/conda-py3.13]
318±1μs 320±3μs 1.01 bench_meteorology.Bench_max_daylight.time_max_daylight [runnervmi13qx/conda-py3.10]
316±0.6μs 317±3μs 1 bench_meteorology.Bench_max_daylight.time_max_daylight [runnervmi13qx/conda-py3.13]
1.30±0.02μs 1.27±0.02μs 0.98 bench_meteorology.Bench_psychrometric_constant.time_psychrometric_constant [runnervmi13qx/conda-py3.10]
1.29±0.04μs 1.26±0μs 0.98 bench_meteorology.Bench_psychrometric_constant.time_psychrometric_constant [runnervmi13qx/conda-py3.13]
25.1±0.2μs 25.6±0.1μs 1.02 bench_meteorology.Bench_relhum.time_relhum [runnervmi13qx/conda-py3.10]
25.1±0.2μs 24.7±0.1μs 0.98 bench_meteorology.Bench_relhum.time_relhum [runnervmi13qx/conda-py3.13]
8.62±0.1μs 8.61±0.1μs 1 bench_meteorology.Bench_saturation_vapor_pressure.time_saturation_vapor_pressure [runnervmi13qx/conda-py3.10]
8.50±0.1μs 8.56±0.1μs 1.01 bench_meteorology.Bench_saturation_vapor_pressure.time_saturation_vapor_pressure [runnervmi13qx/conda-py3.13]
11.0±0.2μs 11.1±0.2μs 1.01 bench_meteorology.Bench_saturation_vapor_pressure_slope.time_saturation_vapor_pressure_slope [runnervmi13qx/conda-py3.10]
10.9±0.1μs 10.8±0.2μs 0.99 bench_meteorology.Bench_saturation_vapor_pressure_slope.time_saturation_vapor_pressure_slope [runnervmi13qx/conda-py3.13]
344M 344M 1 bench_spherical.Bench_spherical.peakmem_decomposition [runnervmi13qx/conda-py3.10]
344M 344M 1 bench_spherical.Bench_spherical.peakmem_decomposition [runnervmi13qx/conda-py3.13]
305M 305M 1 bench_spherical.Bench_spherical.peakmem_recomposition [runnervmi13qx/conda-py3.10]
305M 305M 1 bench_spherical.Bench_spherical.peakmem_recomposition [runnervmi13qx/conda-py3.13]
229M 229M 1 bench_spherical.Bench_spherical.peakmem_scale_voronoi [runnervmi13qx/conda-py3.10]
229M 229M 1 bench_spherical.Bench_spherical.peakmem_scale_voronoi [runnervmi13qx/conda-py3.13]
2.06±0s 2.06±0s 1 bench_spherical.Bench_spherical.time_decomposition [runnervmi13qx/conda-py3.10]
2.07±0s 2.06±0.01s 1 bench_spherical.Bench_spherical.time_decomposition [runnervmi13qx/conda-py3.13]
2.07±0s 2.07±0.01s 1 bench_spherical.Bench_spherical.time_recomposition [runnervmi13qx/conda-py3.10]
2.07±0.01s 2.07±0.01s 1 bench_spherical.Bench_spherical.time_recomposition [runnervmi13qx/conda-py3.13]
250±6ms 239±5ms 0.96 bench_spherical.Bench_spherical.time_scale_voronoi [runnervmi13qx/conda-py3.10]
253±4ms 245±10ms 0.97 bench_spherical.Bench_spherical.time_scale_voronoi [runnervmi13qx/conda-py3.13]
210M 210M 1 bench_stats.Bench_eofunc_eofs.peakmem_eofunc [runnervmi13qx/conda-py3.10]
210M 210M 1 bench_stats.Bench_eofunc_eofs.peakmem_eofunc [runnervmi13qx/conda-py3.13]
9.58±0.2ms 9.72±0.09ms 1.01 bench_stats.Bench_eofunc_eofs.time_eofunc [runnervmi13qx/conda-py3.10]
9.80±0.2ms 9.49±0.2ms 0.97 bench_stats.Bench_eofunc_eofs.time_eofunc [runnervmi13qx/conda-py3.13]
415M 416M 1 bench_stats.Bench_eofunc_ps.peakmem_eofunc_ps [runnervmi13qx/conda-py3.10]
416M 416M 1 bench_stats.Bench_eofunc_ps.peakmem_eofunc_ps [runnervmi13qx/conda-py3.13]
426±5ms 423±2ms 0.99 bench_stats.Bench_eofunc_ps.time_eofunc_ps [runnervmi13qx/conda-py3.10]
426±3ms 419±3ms 0.98 bench_stats.Bench_eofunc_ps.time_eofunc_ps [runnervmi13qx/conda-py3.13]
48.8±0.9μs 49.8±0.1μs 1.02 bench_stats.Bench_pearson_r.time_pearson_r [runnervmi13qx/conda-py3.10]
48.0±0.1μs 49.3±0.3μs 1.03 bench_stats.Bench_pearson_r.time_pearson_r [runnervmi13qx/conda-py3.13]
1.74±0.01s 1.75±0.02s 1 import.Import.timeraw_import_geocat_comp [runnervmi13qx/conda-py3.10]
1.75±0.01s 1.74±0.01s 0.99 import.Import.timeraw_import_geocat_comp [runnervmi13qx/conda-py3.13]

@anissa111 anissa111 marked this pull request as ready for review January 13, 2026 23:55
Copy link
Collaborator

@kafitzgerald kafitzgerald left a comment

Choose a reason for hiding this comment

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

This all looks good to me with perhaps the exception of listing it as a bug fix or something more user facing so that folks can easily find something to understand what happened should they notice a diff in the results.

Copy link
Contributor

@cyschneck cyschneck left a comment

Choose a reason for hiding this comment

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

Looks good! I wonder if this will solve the issues I ran into when attempting to adding heat_index into applications (old PR that should be closed/fixed: NCAR/geocat-applications#124)

@anissa111 anissa111 merged commit ac74483 into NCAR:main Jan 15, 2026
17 checks passed
@anissa111 anissa111 deleted the negsqrt branch January 15, 2026 23:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working run-benchmark Add tag to a PR to run ASV comparison on new commits testing Issue related to testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

heat_index replicates NCL bug(s) heat_index encounters sqrt of negatives

3 participants