8000 Merge pull request #25978 from anntzer/if · matplotlib/matplotlib@d464cbf · GitHub
[go: up one dir, main page]

Skip to content

Commit d464cbf

Browse files
authored
Merge pull request #25978 from anntzer/if
Fix subslice optimization for long, fully nan lines.
2 parents 2f778fd + 2857fde commit d464cbf

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
@@ -268,6 +268,8 @@ class Line2D(Artist):
268268

269269
zorder = 2
270270

271+
_subslice_optim_min_size = 1000
272+
271273
def __str__(self):
272274
if self._label != "":
273275
return f"Line2D({self._label})"
@@ -677,12 +679,14 @@ def recache(self, always=False):
677679
self._x, self._y = self._xy.T # views
678680

679681
self._subslice = False
680-
if (self.axes and len(x) > 1000 and self._is_sorted(x) and
681-
self.axes.name == 'rectilinear' and
682-
self.axes.get_xscale() == 'linear' and
683-
self._markevery is None and
684-
self.get_clip_on() and
685-
self.get_transform() == self.axes.transData):
682+
if (self.axes
683+
and len(x) > self._subslice_optim_min_size
684+
and _path.is_sorted_and_has_non_nan(x)
685+
and self.axes.name == 'rectilinear'
686+
and self.axes.get_xscale() == 'linear'
687+
and self._markevery is None
688+
and self.get_clip_on()
689+
and self.get_transform() == self.axes.transData):
686690
self._subslice = True
687691
nanmask = np.isnan(x)
688692
if nanmask.any():
@@ -731,11 +735,6 @@ def set_transform(self, t):
731735
self._invalidy = True
732736
super().set_transform(t)
733737

734-
def _is_sorted(self, x):
735-
"""Return whether x is sorted in ascending order."""
736-
# We don't handle the monotonically decreasing case.
737-
return _path.is_sorted(x)
738-
739738
@allow_rasterization
740739
def draw(self, renderer):
741740
# 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
@@ -244,11 +245,12 @@ def test_lw_scaling():
244245
ax.plot(th, j*np.ones(50) + .1 * lw, linestyle=ls, lw=lw, **sty)
245246

246247

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

253255

254256
@check_figures_equal()

src/_path.h

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

12251225
template<class T>
1226-
bool is_sorted(PyArrayObject *array)
1226+
bool is_sorted_and_has_non_nan(PyArrayObject *array)
12271227
{
1228-
npy_intp size = PyArray_DIM(array, 0);
1228+
char* ptr = PyArray_BYTES(array);
1229+
npy_intp size = PyArray_DIM(array, 0),
1230+
stride = PyArray_STRIDE(array, 0);
12291231
using limits = std::numeric_limits<T>;
12301232
T last = limits::has_infinity ? -limits::infinity() : limits::min();
1233+
bool found_non_nan = false;
12311234

1232-
for (npy_intp i = 0; i < size; ++i) {
1233-
T current = *(T *)PyArray_GETPTR1(array, i);
1235+
for (npy_intp i = 0; i < size; ++i, ptr += stride) {
1236+
T current = *(T*)ptr;
12341237
// The following tests !isnan(current), but also works for integral
12351238
// types. (The isnan(IntegralType) overload is absent on MSVC.)
12361239
if (current == current) {
1240+
found_non_nan = true;
12371241
if (current < last) {
12381242
return false;
12391243
}
12401244
last = current;
12411245
}
12421246
}
1243-
return true;
1247+
return found_non_nan;
12441248
};
12451249

12461250

src/_path_wrapper.cpp

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

691691

692-
const char *Py_is_sorted__doc__ =
693-
"is_sorted(array)\n"
692+
const char *Py_is_sorted_and_has_non_nan__doc__ =
693+
"is_sorted_and_has_non_nan(array, /)\n"
694694
"--\n\n"
695-
"Return whether the 1D *array* is monotonically increasing, ignoring NaNs.\n";
695+
"Return whether the 1D *array* is monotonically increasing, ignoring NaNs,\n"
696+
"and has at least one non-nan value.";
696697

697-
static PyObject *Py_is_sorted(PyObject *self, PyObject *obj)
698+
static PyObject *Py_is_sorted_and_has_non_nan(PyObject *self, PyObject *obj)
698699
{
699-
npy_intp size;
700700
bool result;
701701

702702
PyArrayObject *array = (PyArrayObject *)PyArray_FromAny(
@@ -706,38 +706,30 @@ static PyObject *Py_is_sorted(PyObject *self, PyObject *obj)
706706
return NULL;
707707
}
708708

709-
size = PyArray_DIM(array, 0);
710-
711-
if (size < 2) {
712-
Py_DECREF(array);
713-
Py_RETURN_TRUE;
714-
}
715-
716-
/* Handle just the most common types here, otherwise coerce to
717-
double */
709+
/* Handle just the most common types here, otherwise coerce to double */
718710
switch (PyArray_TYPE(array)) {
719711
case NPY_INT:
720-
result = is_sorted<npy_int>(array);
712+
result = is_sorted_and_has_non_nan<npy_int>(array);
721713
break;
722714
case NPY_LONG:
723-
result = is_sorted<npy_long>(array);
715+
result = is_sorted_and_has_non_nan<npy_long>(array);
724716
break;
725717
case NPY_LONGLONG:
726-
result = is_sorted<npy_longlong>(array);
718+
result = is_sorted_and_has_non_nan<npy_longlong>(array);
727719
break;
728720
case NPY_FLOAT:
729-
result = is_sorted<npy_float>(array);
721+
result = is_sorted_and_has_non_nan<npy_float>(array);
730722
break;
731723
case NPY_DOUBLE:
732-
result = is_sorted<npy_double>(array);
724+
result = is_sorted_and_has_non_nan<npy_double>(array);
733725
break;
734726
default:
735727
Py_DECREF(array);
736728
array = (PyArrayObject *)PyArray_FromObject(obj, NPY_DOUBLE, 1, 1);
737729
if (array == NULL) {
738730
return NULL;
739731
}
740-
result = is_sorted<npy_double>(array);
732+
result = is_sorted_and_has_non_nan<npy_double>(array);
741733
}
742734

743735
Py_DECREF(array);
@@ -765,7 +757,7 @@ static PyMethodDef module_functions[] = {
765757
{"convert_path_to_polygons", (PyCFunction)Py_convert_path_to_polygons, METH_VARARGS|METH_KEYWORDS, Py_convert_path_to_polygons__doc__},
766758
{"cleanup_path", (PyCFunction)Py_cleanup_path, METH_VARARGS, Py_cleanup_path__doc__},
767759
{"convert_to_string", (PyCFunction)Py_convert_to_string, METH_VARARGS, Py_convert_to_string__doc__},
768-
{"is_sorted", (PyCFunction)Py_is_sorted, METH_O, Py_is_sorted__doc__},
760+
{"is_sorted_and_has_non_nan", (PyCFunction)Py_is_sorted_and_has_non_nan, METH_O, Py_is_sorted_and_has_non_nan__doc__},
769761
{NULL}
770762
};
771763

0 commit comments

Comments
 (0)
0