@@ -126,6 +126,12 @@ double Convolution::regular_integrand(double z, void *p) {
126126 double x = (params->x );
127127 int nf = (params->nf );
128128
129+ // When the integration interval [x, x_max] is very small, a quadrature node can round to
130+ // z = x in double precision, giving x/z = 1 exactly. Some splitting functions (e.g. Pgg1reg)
131+ // produce NaN at x/z = 1 due to 0*inf in harmonic polylogarithms. And given that the true
132+ // integrand is finite there, we can simply return 0.
133+ if (x / z >= 1 .) return 0 .;
134+
129135 return (params->conv )->GetCoeffFunc ()->MuIndependentTerms (z, m2Q2, nf)
130136 * (params->conv )->GetSplitFunc ()->Regular (x / z, nf) / z;
131137}
@@ -198,6 +204,12 @@ double Convolution::singular_integrand(double z, void *p) {
198204 double x = (params->x );
199205 int nf = (params->nf );
200206
207+ // When the integration interval [x/x_max, 1] is very small, a quadrature node can round to
208+ // z = 1 in double precision. At z = 1 the plus-distribution subtraction (f(x/z)/z - f(x))
209+ // vanishes while Singular(z) = 1/(1-z) diverges, producing inf * 0 = NaN. Given that the
210+ // true limit is finite, we can simply return 0.
211+ if (z >= 1 .) return 0 .;
212+
201213 return (params->conv )->GetSplitFunc ()->Singular (z, nf)
202214 * ((params->conv )
203215 ->GetCoeffFunc ()
@@ -468,6 +480,9 @@ double
468480
469481 double z1 = z[0 ], z2 = z[1 ];
470482
483+ // Guard against x/z1 or z1/z2 rounding to 1 (see `regular_integrand`).
484+ if (x / z1 >= 1 . || z1 / z2 >= 1 .) return 0 .;
485+
471486 if (z2 > z1) {
472487 return 1 . / (z1 * z2)
473488 * (params->conv )->GetSplitFunc ()->Regular (x / z1, nf)
@@ -494,6 +509,9 @@ double
494509
495510 double z1 = z[0 ], z2 = z[1 ];
496511
512+ // Guard against x/z1 rounding to 1 or z1/z2 rounding to 1.
513+ if (x / z1 >= 1 . || z1 / z2 >= 1 .) return 0 .;
514+
497515 if (z2 > z1) {
498516 return 1 . / (z1 * z2)
499517 * (params->conv )->GetSplitFunc ()->Regular (x / z1, nf)
@@ -522,6 +540,9 @@ double DoubleConvolution::regular3_integrand(double z, void *p) {
522540 double x = (params->x );
523541 int nf = (params->nf );
524542
543+ // Guard against x/z rounding to 1.
544+ if (x / z >= 1 .) return 0 .;
545+
525546 double x_max = CoefficientFunction::xMax (m2Q2);
526547
527548 return -1 . / z * (params->conv )->GetSplitFunc ()->Regular (x / z, nf)
@@ -599,6 +620,11 @@ double DoubleConvolution::singular1_integrand(
599620
600621 double z1 = z[0 ], z2 = z[1 ];
601622
623+ // At z1 = 1 the plus-distribution subtraction vanishes while Singular diverges,
624+ // producing inf * 0 = NaN. Given that the true integrand is finite, we can simply
625+ // return 0.
626+ if (z1 >= 1 .) return 0 .;
627+
602628 double tmp;
603629 if (z2 > x / z1) {
604630 tmp = (params->conv )->GetSplitFunc ()->Regular (x / (z1 * z2), nf) / z1;
@@ -630,6 +656,10 @@ double DoubleConvolution::singular2_integrand(
630656
631657 double z1 = z[0 ], z2 = z[1 ];
632658
659+ // At z1 = 1 or z2 = 1 the plus-distribution subtraction vanishes while Singular diverges,
660+ // producing inf * 0 = NaN.
661+ if (z1 >= 1 . || z2 >= 1 .) return 0 .;
662+
633663 double tmp;
634664 if (z2 > x / (x_max * z1)) {
635665 tmp = ((params->conv )
@@ -668,6 +698,10 @@ double DoubleConvolution::singular3_integrand(double z, void *p) {
668698 double x = (params->x );
669699 int nf = (params->nf );
670700
701+ // Guard: at z = 1 the plus-distribution subtraction vanishes while Singular diverges,
702+ // producing inf * 0 = NaN.
703+ if (z >= 1 .) return 0 .;
704+
671705 double x_max = CoefficientFunction::xMax (m2Q2);
672706
673707 return -(
0 commit comments