scipy: On PyPy + windows, complex quadpack seem inconsistent
xref conda-forge/scipy-feedstock#196
There are 144 test failures when I try to build scipy 1.7.2 and 1.7.0 with PyPy3.7 on windows. The all are in the TestStudentizedRange.test_pdf_against_mp
tests and look like the the failure below. The test warns IntegrationWarning: The occurrence of roundoff error is detected, which prevents the requested tolerance from being achieved. The error may be underestimated
. Could someone give me a hint where to start looking for the problem? I am building for now with set SCIPY_USE_PYTHRAN=0
but I don’t think that is connected at all. The problem is most likely with PyPy, but I don’t understand the code enough to find a hook to start debugging this. What is special about this test set?
TestStudentizedRange.test_pdf_against_mp
___________________ TestStudentizedRange.test_pdf_against_mp[case_result54] ________
scipy\stats\tests\test_distributions.py:4572: in test_pdf_against_mp
res = stats.studentized_range.pdf(*qkv)
case_result = {'mp_result': 0.43334135870667473, 'src_case': {'expected_atol': 1e-11, 'expected_rtol': 1e-11, 'k': 20, 'q': 4, ...}}
mp_result = 0.43334135870667473
qkv = (4, 20, 50)
self = <scipy.stats.tests.test_distributions.TestStudentizedRange object at 0x0000027796144368>
src_case = {'expected_atol': 1e-11, 'expected_rtol': 1e-11, 'k': 20, 'q': 4, ...}
scipy\stats\_distn_infrastructure.py:1879: in pdf
place(output, cond, self._pdf(*goodargs) / scale)
args = (array(20), array(50))
cond = True
cond0 = True
cond1 = True
dtyp = dtype('float64')
goodargs = [array([4.]), array([20]), array([50])]
kwds = {}
loc = array(0)
output = array(0.)
scale = array([1])
self = <scipy.stats._continuous_distns.studentized_range_gen object at 0x000002778e095cc8>
x = array(4.)
scipy\stats\_continuous_distns.py:9492: in _pdf
return np.float64(ufunc(x, k, df))
_single_pdf = <function studentized_range_gen._pdf.<locals>._single_pdf at 0x00000277959a2020>
cython_symbol = '_studentized_range_pdf'
df = array([50])
k = array([20])
self = <scipy.stats._continuous_distns.studentized_range_gen object at 0x000002778e095cc8>
ufunc = <ufunc '? (vectorized)'>
x = array([4.])
scipy\stats\_continuous_distns.py:9489: in _single_pdf
return integrate.nquad(llc, ranges=ranges, opts=opts)[0]
arg = [4.0, 20, 50, 32.32048465687308]
cython_symbol = '_studentized_range_pdf'
df = 50
k = 20
llc = LowLevelCallable(<capsule object "double (int, double *, void *)" at 0x0000027783549D80>, c_void_p(2712581508336))
log_const = 32.32048465687308
opts = {'epsabs': 1e-11, 'epsrel': 1e-12}
q = 4.0
ranges = [(-inf, inf), (0, inf)]
usr_data = c_void_p(2712581508336)
scipy\integrate\quadpack.py:825: in nquad
return _NQuad(func, ranges, opts, full_output).integrate(*args)
args = ()
depth = 2
full_output = False
func = LowLevelCallable(<capsule object "double (int, double *, void *)" at 0x0000027783549D80>, c_void_p(2712581508336))
opts = [<scipy.integrate.quadpack._OptFunc object at 0x00000277961441a8>, <scipy.integrate.quadpack._OptFunc object at 0x00000277961441a8>]
ranges = [<scipy.integrate.quadpack._RangeFunc object at 0x0000027796144170>, <scipy.integrate.quadpack._RangeFunc object at 0x0000027796144288>]
scipy\integrate\quadpack.py:880: in integrate
**opt)
args = ()
depth = 0
f = functools.partial(<bound method _NQuad.integrate of <scipy.integrate.quadpack._NQuad object at 0x0000027796144138>>, depth=1)
fn_opt = <scipy.integrate.quadpack._OptFunc object at 0x00000277961441a8>
fn_range = <scipy.integrate.quadpack._RangeFunc object at 0x0000027796144288>
high = inf
ind = -1
kwargs = {}
low = 0
opt = {'epsabs': 1e-11, 'epsrel': 1e-12}
self = <scipy.integrate.quadpack._NQuad object at 0x0000027796144138>
scipy\integrate\quadpack.py:352: in quad
points)
a = 0
args = ()
b = inf
epsabs = 1e-11
epsrel = 1e-12
flip = False
full_output = False
func = functools.partial(<bound method _NQuad.integrate of <scipy.integrate.quadpack._NQuad object at 0x0000027796144138>>, depth=1)
limit = 50
limlst = 50
maxp1 = 50
points = None
weight = None
wopts = None
wvar = None
scipy\integrate\quadpack.py:465: in _quad
return _quadpack._qagie(func,bound,infbounds,args,full_output,epsabs,epsrel,limit)
a = 0
args = ()
b = inf
bound = 0
epsabs = 1e-11
epsrel = 1e-12
full_output = False
func = functools.partial(<bound method _NQuad.integrate of <scipy.integrate.quadpack._NQuad object at 0x0000027796144138>>, depth=1)
infbounds = 1
limit = 50
points = None
..\..\..\..\pypy3.7-v7.3.7-win64\lib_pypy\_functools.py:80: in __call__
return self._func(*(self._args + fargs), **fkeywords)
fargs = (-1.0,)
fkeywords = {'depth': 1}
self = functools.partial(<bound method _NQuad.integrate of <scipy.integrate.quadpack._NQuad object at 0x0000027796144138>>, depth=1)
scipy\integrate\quadpack.py:880: in integrate
**opt)
args = (-1.0,)
depth = 1
f = LowLevelCallable(<capsule object "double (int, double *, void *)" at 0x0000027783549D80>, c_void_p(2712581508336))
fn_opt = <scipy.integrate.quadpack._OptFunc object at 0x00000277961441a8>
fn_range = <scipy.integrate.quadpack._RangeFunc object at 0x0000027796144170>
high = inf
ind = -2
kwargs = {}
low = -inf
opt = {'epsabs': 1e-11, 'epsrel': 1e-12}
self = <scipy.integrate.quadpack._NQuad object at 0x0000027796144138>
scipy\integrate\quadpack.py:400: in quad
warnings.warn(msg, IntegrationWarning, stacklevel=2)
E scipy.integrate.quadpack.IntegrationWarning: The occurrence of roundoff error is detected, which prevents
E the requested tolerance from being achieved. The error may be
E underestimated.
a = -inf
args = (-1.0,)
b = inf
epsabs = 1e-11
epsrel = 1e-12
flip = False
full_output = False
func = LowLevelCallable(<capsule object "double (int, double *, void *)" at 0x0000027783549D80>, c_void_p(2712581508336))
ier = 2
limit = 50
limlst = 50
maxp1 = 50
msg = 'The occurrence of roundoff error is detected, which prevents \n the requested tolerance from being achieved. The error may be \n underestimated.'
msgs = {80: 'A Python error occurred possibly while calling the function.', 1: 'The maximum number of subdivisions (50) has b...\n underestimated.', 3: 'Extremely bad integrand behavior occurs at some points of the\n integration interval.', ...}
points = None
retval = (nan, nan, 2)
weight = None
wopts = None
wvar = None
</detail>About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 41 (41 by maintainers)
I have a smaller reproducer in https://gist.github.com/mattip/346234bc51a8a3afca10ea8929a53df0. The problem only happens when PyPy JIT kicks in: eventually the python callback in
dqegie
is called with-1
as an argument instead of a valid value. I am trying to work out how that happens. I have ruled out FPU precision by checking_control87
. It may be some kind of argument passing bug.One thing is strange. When building scipy, I see warnings that
small
may be used uninitialized https://github.com/scipy/scipy/blob/62f75fa33f1a832043b53f0694693dee0fe9503a/scipy/integrate/quadpack/dqagie.f#L411That code seems to be 21 years old, so it must have been vetted by now. Is there a problem or is it just my fortran skills are rusty?
Good idea! Along those lines, I figured out how to wrap the Cython to inspect inputs/outputs and extracted the distribution’s implementation for testing. Good news - the problem is still there.
Here’s the output of my test batch script.
The
CUSTOM
output uses the extracted implementation, and is compiled by each respective interpreter. It should matchSCIPY
because they’re effectively same.When compiled/ran by pypy, both the PDF and moment come out wrong vs the reference CPython run. What’s very interesting is that - despite the garbage output - the PyPy and CPython PDF integrands (the cython code) evaluate to the exact same values over a very large input set (the "Compare) section). That seems to rule out a studentized range compilation problem to me, at least for my problem. If you want to check what happens in your environment, I dumped the code here https://github.com/DominicChm/IntegrandTest. The above output is from
compare.bat