diff options
-rw-r--r-- | .travis.yml | 22 | ||||
-rw-r--r-- | doc/release/1.10.4-notes.rst | 2 | ||||
-rw-r--r-- | doc/release/1.11.0-notes.rst | 11 | ||||
-rw-r--r-- | numpy/core/src/umath/loops.c.src | 9 | ||||
-rw-r--r-- | numpy/core/src/umath/scalarmath.c.src | 55 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 41 | ||||
-rw-r--r-- | numpy/core/tests/test_scalarmath.py | 94 | ||||
-rwxr-xr-x | runtests.py | 24 |
8 files changed, 205 insertions, 53 deletions
diff --git a/.travis.yml b/.travis.yml index 1832e317c..3066cbbaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,16 @@ cache: directories: - $HOME/.cache/pip +env: + global: + - WHEELHOUSE_UPLOADER_USERNAME=travis.numpy + # The following is generated with the command: + # travis encrypt -r numpy/numpy WHEELHOUSE_UPLOADER_SECRET=tH3AP1KeY + - secure: "IEicLPrP2uW+jW51GRwkONQpdPqMVtQL5bdroqR/U8r9Tr\ + XrbCVRhp4AP8JYZT0ptoBpmZWWGjmKBndB68QlMiUjQPow\ + iFWt9Ka92CaqYdU7nqfWp9VImSndPmssjmCXJ1v1IjZPAM\ + ahp7Qnm0rWRmA0z9SomuRUQOJQ6s684vU=" + python: - 2.6 - 2.7 @@ -51,16 +61,10 @@ matrix: - python3-setuptools - python: 2.7 env: NPY_RELAXED_STRIDES_CHECKING=0 PYTHON_OO=1 + - python: 2.7 + env: USE_WHEEL=1 - python: 3.5 - env: - - USE_WHEEL=1 - - WHEELHOUSE_UPLOADER_USERNAME=travis.numpy - # The following is generated with the command: - # travis encrypt -r numpy/numpy WHEELHOUSE_UPLOADER_SECRET=tH3AP1KeY - - secure: "IEicLPrP2uW+jW51GRwkONQpdPqMVtQL5bdroqR/U8r9Tr\ - XrbCVRhp4AP8JYZT0ptoBpmZWWGjmKBndB68QlMiUjQPow\ - iFWt9Ka92CaqYdU7nqfWp9VImSndPmssjmCXJ1v1IjZPAM\ - ahp7Qnm0rWRmA0z9SomuRUQOJQ6s684vU=" + env: USE_WHEEL=1 - python: 2.7 env: - PYTHONOPTIMIZE=2 diff --git a/doc/release/1.10.4-notes.rst b/doc/release/1.10.4-notes.rst index 03eaf5e6b..7de732a22 100644 --- a/doc/release/1.10.4-notes.rst +++ b/doc/release/1.10.4-notes.rst @@ -25,7 +25,7 @@ Issues Fixed Merged PRs ========== -The following PRs have been merged into 1.10.3. When the PR is a backport, +The following PRs have been merged into 1.10.4. When the PR is a backport, the PR number for the original PR against master is listed. * gh-6840 TST: Update travis testing script in 1.10.x diff --git a/doc/release/1.11.0-notes.rst b/doc/release/1.11.0-notes.rst index c4ff89230..0305688d8 100644 --- a/doc/release/1.11.0-notes.rst +++ b/doc/release/1.11.0-notes.rst @@ -8,9 +8,14 @@ Highlights ========== -Dropped Support -=============== - +Build System Changes +==================== + +* Numpy now uses ``setuptools`` for its builds instead of plain distutils. + This fixes usage of ``install_requires='numpy'`` in the ``setup.py`` files of + projects that depend on Numpy (see gh-6551). It potentially affects the way + that build/install methods for Numpy itself behave though. Please report any + unexpected behavior on the Numpy issue tracker. * Bento build support and related files have been removed. * Single file build support and related files have been removed. diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 563761bc0..e74ac9d40 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -1688,13 +1688,8 @@ NPY_NO_EXPORT void BINARY_LOOP { const @type@ in1 = *(@type@ *)ip1; const @type@ in2 = *(@type@ *)ip2; - const @type@ res = npy_fmod@c@(in1,in2); - if (res && ((in2 < 0) != (res < 0))) { - *((@type@ *)op1) = res + in2; - } - else { - *((@type@ *)op1) = res; - } + const @type@ div = in1/in2; + *((@type@ *)op1) = in2*(div - npy_floor@c@(div)); } } diff --git a/numpy/core/src/umath/scalarmath.c.src b/numpy/core/src/umath/scalarmath.c.src index c371a079f..706eccb31 100644 --- a/numpy/core/src/umath/scalarmath.c.src +++ b/numpy/core/src/umath/scalarmath.c.src @@ -272,10 +272,10 @@ static void static @type@ (*_basic_@name@_floor)(@type@); static @type@ (*_basic_@name@_sqrt)(@type@); static @type@ (*_basic_@name@_fmod)(@type@, @type@); -#define @name@_ctype_add(a, b, outp) *(outp) = a + b -#define @name@_ctype_subtract(a, b, outp) *(outp) = a - b -#define @name@_ctype_multiply(a, b, outp) *(outp) = a * b -#define @name@_ctype_divide(a, b, outp) *(outp) = a / b +#define @name@_ctype_add(a, b, outp) *(outp) = (a) + (b) +#define @name@_ctype_subtract(a, b, outp) *(outp) = (a) - (b) +#define @name@_ctype_multiply(a, b, outp) *(outp) = (a) * (b) +#define @name@_ctype_divide(a, b, outp) *(outp) = (a) / (b) #define @name@_ctype_true_divide @name@_ctype_divide #define @name@_ctype_floor_divide(a, b, outp) \ *(outp) = _basic_@name@_floor((a) / (b)) @@ -316,16 +316,32 @@ static npy_half (*_basic_half_fmod)(npy_half, npy_half); (outp)->real = (a).real * (b).real - (a).imag * (b).imag; \ (outp)->imag = (a).real * (b).imag + (a).imag * (b).real; \ } while(0) -/* Note: complex division by zero must yield some complex inf */ +/* Algorithm identical to that in loops.c.src, for consistency */ #define @name@_ctype_divide(a, b, outp) do{ \ - @rtype@ d = (b).real*(b).real + (b).imag*(b).imag; \ - if (d != 0) { \ - (outp)->real = ((a).real*(b).real + (a).imag*(b).imag)/d; \ - (outp)->imag = ((a).imag*(b).real - (a).real*(b).imag)/d; \ + @rtype@ in1r = (a).real; \ + @rtype@ in1i = (a).imag; \ + @rtype@ in2r = (b).real; \ + @rtype@ in2i = (b).imag; \ + @rtype@ in2r_abs = npy_fabs@c@(in2r); \ + @rtype@ in2i_abs = npy_fabs@c@(in2i); \ + if (in2r_abs >= in2i_abs) { \ + if (in2r_abs == 0 && in2i_abs == 0) { \ + /* divide by zero should yield a complex inf or nan */ \ + (outp)->real = in1r/in2r_abs; \ + (outp)->imag = in1i/in2i_abs; \ + } \ + else { \ + @rtype@ rat = in2i/in2r; \ + @rtype@ scl = 1.0@c@/(in2r + in2i*rat); \ + (outp)->real = (in1r + in1i*rat)*scl; \ + (outp)->imag = (in1i - in1r*rat)*scl; \ + } \ } \ else { \ - (outp)->real = (a).real/d; \ - (outp)->imag = (a).imag/d; \ + @rtype@ rat = in2r/in2i; \ + @rtype@ scl = 1.0@c@/(in2i + in2r*rat); \ + (outp)->real = (in1r*rat + in1i)*scl; \ + (outp)->imag = (in1i*rat - in1r)*scl; \ } \ } while(0) #define @name@_ctype_true_divide @name@_ctype_divide @@ -343,23 +359,16 @@ static npy_half (*_basic_half_fmod)(npy_half, npy_half); */ static void @name@_ctype_remainder(@type@ a, @type@ b, @type@ *out) { - @type@ mod; - mod = _basic_@name@_fmod(a, b); - if (mod && (((b < 0) != (mod < 0)))) { - mod += b; - } - *out = mod; + @type@ tmp = a/b; + *out = b * (tmp - _basic_@name@_floor(tmp)); } /**end repeat**/ static void half_ctype_remainder(npy_half a, npy_half b, npy_half *out) { - float mod, fa = npy_half_to_float(a), fb = npy_half_to_float(b); - mod = _basic_float_fmod(fa, fb); - if (mod && (((fb < 0) != (mod < 0)))) { - mod += fb; - } - *out = npy_float_to_half(mod); + float tmp, fa = npy_half_to_float(a), fb = npy_half_to_float(b); + float_ctype_remainder(fa, fb, &tmp); + *out = npy_float_to_half(tmp); } diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 26617c1fc..1666af4f1 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -143,7 +143,8 @@ class TestAttributes(TestCase): numpy_int = np.int_(0) if sys.version_info[0] >= 3: - # On Py3k int_ should not inherit from int, because it's not fixed-width anymore + # On Py3k int_ should not inherit from int, because it's not + # fixed-width anymore assert_equal(isinstance(numpy_int, int), False) else: # Otherwise, it should inherit from int... @@ -175,7 +176,8 @@ class TestAttributes(TestCase): def make_array(size, offset, strides): try: - r = np.ndarray([size], dtype=int, buffer=x, offset=offset*x.itemsize) + r = np.ndarray([size], dtype=int, buffer=x, + offset=offset*x.itemsize) except: raise RuntimeError(getexception()) r.strides = strides = strides*x.itemsize @@ -2327,6 +2329,41 @@ class TestMethods(TestCase): assert_raises(AttributeError, lambda: a.conj()) assert_raises(AttributeError, lambda: a.conjugate()) + def test_divmod_basic(self): + dt = np.typecodes['AllInteger'] + np.typecodes['Float'] + for dt1, dt2 in itertools.product(dt, dt): + for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): + if sg1 == -1 and dt1 in np.typecodes['UnsignedInteger']: + continue + if sg2 == -1 and dt2 in np.typecodes['UnsignedInteger']: + continue + fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s' + msg = fmt % (dt1, dt2, sg1, sg2) + a = np.array(sg1*71, dtype=dt1) + b = np.array(sg2*19, dtype=dt2) + div, rem = divmod(a, b) + assert_allclose(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) + + def test_divmod_roundoff(self): + # gh-6127 + dt = 'fdg' + for dt1, dt2 in itertools.product(dt, dt): + for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): + fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s' + msg = fmt % (dt1, dt2, sg1, sg2) + a = np.array(sg1*78*6e-8, dtype=dt1) + b = np.array(sg2*6e-8, dtype=dt2) + div, rem = divmod(a, b) + assert_allclose(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) + class TestBinop(object): def test_inplace(self): diff --git a/numpy/core/tests/test_scalarmath.py b/numpy/core/tests/test_scalarmath.py index 6dd9aa455..17f70f6c9 100644 --- a/numpy/core/tests/test_scalarmath.py +++ b/numpy/core/tests/test_scalarmath.py @@ -1,12 +1,13 @@ from __future__ import division, absolute_import, print_function import sys +import itertools import numpy as np from numpy.testing.utils import _gen_alignment_data from numpy.testing import ( TestCase, run_module_suite, assert_, assert_equal, assert_raises, - assert_almost_equal + assert_almost_equal, assert_allclose ) types = [np.bool_, np.byte, np.ubyte, np.short, np.ushort, np.intc, np.uintc, @@ -135,6 +136,44 @@ class TestPower(TestCase): else: assert_almost_equal(result, 9, err_msg=msg) + +class TestDivmod(TestCase): + def test_divmod_basic(self): + dt = np.typecodes['AllInteger'] + np.typecodes['Float'] + for dt1, dt2 in itertools.product(dt, dt): + for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): + if sg1 == -1 and dt1 in np.typecodes['UnsignedInteger']: + continue + if sg2 == -1 and dt2 in np.typecodes['UnsignedInteger']: + continue + fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s' + msg = fmt % (dt1, dt2, sg1, sg2) + a = np.array(sg1*71, dtype=dt1)[()] + b = np.array(sg2*19, dtype=dt2)[()] + div, rem = divmod(a, b) + assert_allclose(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) + + def test_divmod_roundoff(self): + # gh-6127 + dt = 'fdg' + for dt1, dt2 in itertools.product(dt, dt): + for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): + fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s' + msg = fmt % (dt1, dt2, sg1, sg2) + a = np.array(sg1*78*6e-8, dtype=dt1)[()] + b = np.array(sg2*6e-8, dtype=dt2)[()] + div, rem = divmod(a, b) + assert_allclose(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) + + class TestComplexDivision(TestCase): def test_zero_division(self): with np.errstate(all="ignore"): @@ -153,6 +192,59 @@ class TestComplexDivision(TestCase): b = t(0.) assert_(np.isnan(b/a)) + def test_signed_zeros(self): + with np.errstate(all="ignore"): + for t in [np.complex64, np.complex128]: + # tupled (numerator, denominator, expected) + # for testing as expected == numerator/denominator + data = ( + (( 0.0,-1.0), ( 0.0, 1.0), (-1.0,-0.0)), + (( 0.0,-1.0), ( 0.0,-1.0), ( 1.0,-0.0)), + (( 0.0,-1.0), (-0.0,-1.0), ( 1.0, 0.0)), + (( 0.0,-1.0), (-0.0, 1.0), (-1.0, 0.0)), + (( 0.0, 1.0), ( 0.0,-1.0), (-1.0, 0.0)), + (( 0.0,-1.0), ( 0.0,-1.0), ( 1.0,-0.0)), + ((-0.0,-1.0), ( 0.0,-1.0), ( 1.0,-0.0)), + ((-0.0, 1.0), ( 0.0,-1.0), (-1.0,-0.0)) + ) + for cases in data: + n = cases[0] + d = cases[1] + ex = cases[2] + result = t(complex(n[0], n[1])) / t(complex(d[0], d[1])) + # check real and imag parts separately to avoid comparison + # in array context, which does not account for signed zeros + assert_equal(result.real, ex[0]) + assert_equal(result.imag, ex[1]) + + def test_branches(self): + with np.errstate(all="ignore"): + for t in [np.complex64, np.complex128]: + # tupled (numerator, denominator, expected) + # for testing as expected == numerator/denominator + data = list() + + # trigger branch: real(fabs(denom)) > imag(fabs(denom)) + # followed by else condition as neither are == 0 + data.append((( 2.0, 1.0), ( 2.0, 1.0), (1.0, 0.0))) + + # trigger branch: real(fabs(denom)) > imag(fabs(denom)) + # followed by if condition as both are == 0 + # is performed in test_zero_division(), so this is skipped + + # trigger else if branch: real(fabs(denom)) < imag(fabs(denom)) + data.append((( 1.0, 2.0), ( 1.0, 2.0), (1.0, 0.0))) + + for cases in data: + n = cases[0] + d = cases[1] + ex = cases[2] + result = t(complex(n[0], n[1])) / t(complex(d[0], d[1])) + # check real and imag parts separately to avoid comparison + # in array context, which does not account for signed zeros + assert_equal(result.real, ex[0]) + assert_equal(result.imag, ex[1]) + class TestConversion(TestCase): def test_int_from_long(self): diff --git a/runtests.py b/runtests.py index 957cbef10..52905a8fc 100755 --- a/runtests.py +++ b/runtests.py @@ -135,8 +135,13 @@ def main(argv): if not args.no_build: site_dir = build_project(args) - sys.path.insert(0, site_dir) - os.environ['PYTHONPATH'] = site_dir + for dirname in os.listdir(site_dir): + if dirname.startswith('numpy'): + # The .pth file isn't re-parsed, so need to put the numpy egg + # produced by easy-install on the path manually. + egg_dir = os.path.join(site_dir, dirname) + sys.path.insert(0, egg_dir) + os.environ['PYTHONPATH'] = egg_dir extra_argv = args.args[:] if extra_argv and extra_argv[0] == '--': @@ -346,6 +351,14 @@ def build_project(args): cmd += ["-j", str(args.parallel)] cmd += ['install', '--prefix=' + dst_dir] + from distutils.sysconfig import get_python_lib + site_dir = get_python_lib(prefix=dst_dir, plat_specific=True) + # easy_install won't install to a path that Python by default cannot see + # and isn't on the PYTHONPATH. Plus, it has to exist. + if not os.path.exists(site_dir): + os.makedirs(site_dir) + env['PYTHONPATH'] = site_dir + log_filename = os.path.join(ROOT_DIR, 'build.log') if args.show_build_log: @@ -383,9 +396,6 @@ def build_project(args): print("Build failed!") sys.exit(1) - from distutils.sysconfig import get_python_lib - site_dir = get_python_lib(prefix=dst_dir, plat_specific=True) - return site_dir @@ -421,8 +431,8 @@ def lcov_generate(): '--output-file', LCOV_OUTPUT_FILE]) print("Generating lcov HTML output...") - ret = subprocess.call(['genhtml', '-q', LCOV_OUTPUT_FILE, - '--output-directory', LCOV_HTML_DIR, + ret = subprocess.call(['genhtml', '-q', LCOV_OUTPUT_FILE, + '--output-directory', LCOV_HTML_DIR, '--legend', '--highlight']) if ret != 0: print("genhtml failed!") |