Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6f878a8

Browse files
authored
Merge pull request #26009 from meeseeksmachine/auto-backport-of-pr-25978-on-v3.7.x
Backport PR #25978 on branch v3.7.x (Fix subslice optimization for long, fully nan lines.)
2 parents 648a74a + f1465ff commit 6f878a8

File tree

4 files changed

+39
-42
lines changed

4 files changed

+39
-42
lines changed

lib/matplotlib/lines.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ class Line2D(Artist):
256256

257257
zorder = 2
258258

259+
_subslice_optim_min_size = 1000
260+
259261
def __str__(self):
260262
if self._label != "":
261263
return f"Line2D({self._label})"
@@ -667,12 +669,14 @@ def recache(self, always=False):
667669
self._x, self._y = self._xy.T # views
668670

669671
self._subslice = False
670-
if (self.axes and len(x) > 1000 and self._is_sorted(x) and
671-
self.axes.name == 'rectilinear' and
672-
self.axes.get_xscale() == 'linear' and
673-
self._markevery is None and
674-
self.get_clip_on() and
675-
self.get_transform() == self.axes.transData):
672+
if (self.axes
673+
and len(x) > self._subslice_optim_min_size
674+
and _path.is_sorted_and_has_non_nan(x)
675+
and self.axes.name == 'rectilinear'
676+
and self.axes.get_xscale() == 'linear'
677+
and self._markevery is None
678+
and self.get_clip_on()
679+
and self.get_transform() == self.axes.transData):
676680
self._subslice = True
677681
nanmask = np.isnan(x)
678682
if nanmask.any():
@@ -721,11 +725,6 @@ def set_transform(self, t):
721725
self._invalidy = True
722726
super().set_transform(t)
723727

724-
def _is_sorted(self, x):
725-
"""Return whether x is sorted in ascending order."""
726-
# We don't handle the monotonically decreasing case.
727-
return _path.is_sorted(x)
728-
729728
@allow_rasterization
730729
def draw(self, renderer):
731730
# docstring inherited

lib/matplotlib/tests/test_lines.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import matplotlib
1616
import matplotlib as mpl
17+
from matplotlib import _path
1718
import matplotlib.lines as mlines
1819
from matplotlib.markers import MarkerStyle
1920
from matplotlib.path import Path
@@ -243,11 +244,12 @@ def test_lw_scaling():
243244
ax.plot(th, j*np.ones(50) + .1 * lw, linestyle=ls, lw=lw, **sty)
244245

245246

246-
def test_nan_is_sorted():
247-
line = mlines.Line2D([], [])
248-
assert line._is_sorted(np.array([1, 2, 3]))
249-
assert line._is_sorted(np.array([1, np.nan, 3]))
250-
assert not line._is_sorted([3, 5] + [np.nan] * 100 + [0, 2])
247+
def test_is_sorted_and_has_non_nan():
248+
assert _path.is_sorted_and_has_non_nan(np.array([1, 2, 3]))
249+
assert _path.is_sorted_and_has_non_nan(np.array([1, np.nan, 3]))
250+
assert not _path.is_sorted_and_has_non_nan([3, 5] + [np.nan] * 100 + [0, 2])
251+
n = 2 * mlines.Line2D._subslice_optim_min_size
252+
plt.plot([np.nan] * n, range(n))
251253

252254

253255
@check_figures_equal()

src/_path.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,24 +1244,28 @@ bool convert_to_string(PathIterator &path,
12441244
}
12451245

12461246
template<class T>
1247-
bool is_sorted(PyArrayObject *array)
1247+
bool is_sorted_and_has_non_nan(PyArrayObject *array)
12481248
{
1249-
npy_intp size = PyArray_DIM(array, 0);
1249+
char* ptr = PyArray_BYTES(array);
1250+
npy_intp size = PyArray_DIM(array, 0),
1251+
stride = PyArray_STRIDE(array, 0);
12501252
using limits = std::numeric_limits<T>;
12511253
T last = limits::has_infinity ? -limits::infinity() : limits::min();
1254+
bool found_non_nan = false;
12521255

1253-
for (npy_intp i = 0; i < size; ++i) {
1254-
T current = *(T *)PyArray_GETPTR1(array, i);
1256+
for (npy_intp i = 0; i < size; ++i, ptr += stride) {
1257+
T current = *(T*)ptr;
12551258
// The following tests !isnan(current), but also works for integral
12561259
// types. (The isnan(IntegralType) overload is absent on MSVC.)
12571260
if (current == current) {
1261+
found_non_nan = true;
12581262
if (current < last) {
12591263
return false;
12601264
}
12611265
last = current;
12621266
}
12631267
}
1264-
return true;
1268+
return found_non_nan;
12651269
};
12661270

12671271

src/_path_wrapper.cpp

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -781,14 +781,14 @@ static PyObject *Py_convert_to_string(PyObject *self, PyObject *args)
781781
}
782782

783783

784-
const char *Py_is_sorted__doc__ =
785-
"is_sorted(array)\n"
784+
const char *Py_is_sorted_and_has_non_nan__doc__ =
785+
"is_sorted_and_has_non_nan(array, /)\n"
786786
"--\n\n"
787-
"Return whether the 1D *array* is monotonically increasing, ignoring NaNs.\n";
787+
"Return whether the 1D *array* is monotonically increasing, ignoring NaNs,\n"
788+
"and has at least one non-nan value.";
788789

789-
static PyObject *Py_is_sorted(PyObject *self, PyObject *obj)
790+
static PyObject *Py_is_sorted_and_has_non_nan(PyObject *self, PyObject *obj)
790791
{
791-
npy_intp size;
792792
bool result;
793793

794794
PyArrayObject *array = (PyArrayObject *)PyArray_FromAny(
@@ -798,38 +798,30 @@ static PyObject *Py_is_sorted(PyObject *self, PyObject *obj)
798798
return NULL;
799799
}
800800

801-
size = PyArray_DIM(array, 0);
802-
803-
if (size < 2) {
804-
Py_DECREF(array);
805-
Py_RETURN_TRUE;
806-
}
807-
808-
/* Handle just the most common types here, otherwise coerce to
809-
double */
801+
/* Handle just the most common types here, otherwise coerce to double */
810802
switch (PyArray_TYPE(array)) {
811803
case NPY_INT:
812-
result = is_sorted<npy_int>(array);
804+
result = is_sorted_and_has_non_nan<npy_int>(array);
813805
break;
814806
case NPY_LONG:
815-
result = is_sorted<npy_long>(array);
807+
result = is_sorted_and_has_non_nan<npy_long>(array);
816808
break;
817809
case NPY_LONGLONG:
818-
result = is_sorted<npy_longlong>(array);
810+
result = is_sorted_and_has_non_nan<npy_longlong>(array);
819811
break;
820812
case NPY_FLOAT:
821-
result = is_sorted<npy_float>(array);
813+
result = is_sorted_and_has_non_nan<npy_float>(array);
822814
break;
823815
case NPY_DOUBLE:
824-
result = is_sorted<npy_double>(array);
816+
result = is_sorted_and_has_non_nan<npy_double>(array);
825817
break;
826818
default:
827819
Py_DECREF(array);
828820
array = (PyArrayObject *)PyArray_FromObject(obj, NPY_DOUBLE, 1, 1);
829821
if (array == NULL) {
830822
return NULL;
831823
}
832-
result = is_sorted<npy_double>(array);
824+
result = is_sorted_and_has_non_nan<npy_double>(array);
833825
}
834826

835827
Py_DECREF(array);
@@ -860,7 +852,7 @@ static PyMethodDef module_functions[] = {
860852
{"convert_path_to_polygons", (PyCFunction)Py_convert_path_to_polygons, METH_VARARGS|METH_KEYWORDS, Py_convert_path_to_polygons__doc__},
861853
{"cleanup_path", (PyCFunction)Py_cleanup_path, METH_VARARGS, Py_cleanup_path__doc__},
862854
{"convert_to_string", (PyCFunction)Py_convert_to_string, METH_VARARGS, Py_convert_to_string__doc__},
863-
{"is_sorted", (PyCFunction)Py_is_sorted, METH_O, Py_is_sorted__doc__},
855+
{"is_sorted_and_has_non_nan", (PyCFunction)Py_is_sorted_and_has_non_nan, METH_O, Py_is_sorted_and_has_non_nan__doc__},
864856
{NULL}
865857
};
866858

0 commit comments

Comments
 (0)