From d0834e307f545dc946b316a068bddc56abf48075 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 22 Dec 2015 15:30:09 -0500 Subject: [PATCH 1/2] Fix #5693: Implemented is_sorted in C --- lib/matplotlib/lines.py | 5 ++-- lib/matplotlib/tests/test_lines.py | 5 ++-- src/_path.h | 38 ++++++++++++++++++++++++++++++ src/_path_wrapper.cpp | 36 ++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 5e65ce1356d3..c966a44dd554 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -33,6 +33,7 @@ from matplotlib.markers import ( CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN, CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE) +from matplotlib import _path def segment_hits(cx, cy, x, y, radius): @@ -702,9 +703,7 @@ def set_transform(self, t): def _is_sorted(self, x): """return True if x is sorted in ascending order""" # We don't handle the monotonically decreasing case. - if len(x) < 2: - return True - return np.nanmin(x[1:] - x[:-1]) >= 0 + return _path.is_sorted(x) @allow_rasterization def draw(self, renderer): diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index 19c8b8173c60..912e53dc9468 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -158,8 +158,9 @@ def test_marker_fill_styles(): def test_nan_is_sorted(): # Exercises issue from PR #2744 (NaN throwing warning in _is_sorted) line = mlines.Line2D([],[]) - assert_true(line._is_sorted(np.array([1,2,3]))) - assert_true(not line._is_sorted(np.array([1,np.nan,3]))) + assert_true(line._is_sorted(np.array([1, 2, 3]))) + assert_true(line._is_sorted(np.array([1, np.nan, 3]))) + assert_true(not line._is_sorted([3, 5] + [np.nan] * 100 + [0, 2])) if __name__ == '__main__': diff --git a/src/_path.h b/src/_path.h index 85032b3bf836..2b564f9c6faf 100644 --- a/src/_path.h +++ b/src/_path.h @@ -1183,4 +1183,42 @@ int convert_to_string(PathIterator &path, } +template +struct _is_sorted +{ + bool operator()(PyArrayObject *array) + { + npy_intp size; + npy_intp i; + T last_value; + T current_value; + + size = PyArray_DIM(array, 0); + + for (i = 0; i < size; ++i) { + last_value = *((T *)PyArray_GETPTR1(array, i)); + if (std::isfinite(last_value)) { + break; + } + } + + if (i == size) { + // The whole array is non-finite + return false; + } + + for (; i < size; ++i) { + current_value = *((T *)PyArray_GETPTR1(array, i)); + if (std::isfinite(current_value)) { + if (current_value < last_value) { + return false; + } + last_value = current_value; + } + } + + return true; + } +}; + #endif diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 2ef17ecc573b..715a519cb0ab 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -726,6 +726,41 @@ static PyObject *Py_convert_to_string(PyObject *self, PyObject *args, PyObject * return result; } + +const char *Py_is_sorted__doc__ = "is_sorted(array)\n\n" + "Returns True if 1-D array is monotonically increasing, ignoring NaNs\n"; + +static PyObject *Py_is_sorted(PyObject *self, PyObject *obj) +{ + npy_intp size; + bool result; + + PyArrayObject *array = (PyArrayObject *)PyArray_FromObject(obj, NPY_DOUBLE, 1, 1); + + if (array == NULL) { + return NULL; + } + + size = PyArray_DIM(array, 0); + + if (size < 2) { + Py_DECREF(array); + Py_RETURN_TRUE; + } + + _is_sorted is_sorted; + result = is_sorted(array); + + Py_DECREF(array); + + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + + extern "C" { static PyMethodDef module_functions[] = { @@ -745,6 +780,7 @@ extern "C" { {"convert_path_to_polygons", (PyCFunction)Py_convert_path_to_polygons, METH_VARARGS, Py_convert_path_to_polygons__doc__}, {"cleanup_path", (PyCFunction)Py_cleanup_path, METH_VARARGS, Py_cleanup_path__doc__}, {"convert_to_string", (PyCFunction)Py_convert_to_string, METH_VARARGS, Py_convert_to_string__doc__}, + {"is_sorted", (PyCFunction)Py_is_sorted, METH_O, Py_is_sorted__doc__}, {NULL} }; From 5c4f237139db96e798cc95869ef418abddbc1ec2 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 22 Dec 2015 17:17:07 -0500 Subject: [PATCH 2/2] Handle more types directly --- src/_path.h | 28 ++++++++++++++++++++++ src/_path_wrapper.cpp | 56 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/_path.h b/src/_path.h index 2b564f9c6faf..d414fca8f53b 100644 --- a/src/_path.h +++ b/src/_path.h @@ -1221,4 +1221,32 @@ struct _is_sorted } }; + +template +struct _is_sorted_int +{ + bool operator()(PyArrayObject *array) + { + npy_intp size; + npy_intp i; + T last_value; + T current_value; + + size = PyArray_DIM(array, 0); + + last_value = *((T *)PyArray_GETPTR1(array, 0)); + + for (i = 1; i < size; ++i) { + current_value = *((T *)PyArray_GETPTR1(array, i)); + if (current_value < last_value) { + return false; + } + last_value = current_value; + } + + return true; + } +}; + + #endif diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 715a519cb0ab..4a4fc2db1eb3 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -735,7 +735,8 @@ static PyObject *Py_is_sorted(PyObject *self, PyObject *obj) npy_intp size; bool result; - PyArrayObject *array = (PyArrayObject *)PyArray_FromObject(obj, NPY_DOUBLE, 1, 1); + PyArrayObject *array = (PyArrayObject *)PyArray_FromAny( + obj, NULL, 1, 1, 0, NULL); if (array == NULL) { return NULL; @@ -748,8 +749,57 @@ static PyObject *Py_is_sorted(PyObject *self, PyObject *obj) Py_RETURN_TRUE; } - _is_sorted is_sorted; - result = is_sorted(array); + /* Handle just the most common types here, otherwise coerce to + double */ + switch(PyArray_TYPE(array)) { + case NPY_INT: + { + _is_sorted_int is_sorted; + result = is_sorted(array); + } + break; + + case NPY_LONG: + { + _is_sorted_int is_sorted; + result = is_sorted(array); + } + break; + + case NPY_LONGLONG: + { + _is_sorted_int is_sorted; + result = is_sorted(array); + } + break; + + case NPY_FLOAT: + { + _is_sorted is_sorted; + result = is_sorted(array); + } + break; + + case NPY_DOUBLE: + { + _is_sorted is_sorted; + result = is_sorted(array); + } + break; + + default: + { + Py_DECREF(array); + array = (PyArrayObject *)PyArray_FromObject(obj, NPY_DOUBLE, 1, 1); + + if (array == NULL) { + return NULL; + } + + _is_sorted is_sorted; + result = is_sorted(array); + } + } Py_DECREF(array);