matplotlib / matplotlib/matplotlib

[Bug]: Ragged Poly3DCollection projects uninitialized padding, causing nondeterministic overflow

Open
#32,272 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

first-contribution topic: mplot3d
Dominant language
Python
Stars
23.2k
Forks
8.5k
Avg merge
1d 6h
Merged PRs (30d)
66

Description

### Bug summary

Matplotlib 3.11.0 can project uninitialized padding when `Poly3DCollection` receives polygons with different numbers of vertices. This occurs with `Axes3D.plot_surface()` when its default downsampling produces non-uniform surface patches.

### Code for reproduction

```Python
import io

import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d.art3d import Poly3DCollection

# Different vertex counts make Poly3DCollection create padded storage.
square = np.zeros((4, 3))
triangle = np.zeros((3, 3))
collection = Poly3DCollection([square, triangle])

# Simulate a large value left in the uninitialized padding. Matplotlib
# already marks this entry as invalid, so it should never be projected.
collection._faces[collection._invalid_vertices] = np.finfo(float).max

figure = plt.figure()
axis = figure.add_subplot(projection="3d")
axis.add_collection(collection, autolim=False)
axis.set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1))

figure.savefig(io.BytesIO(), format="jpg")
```

### Actual outcome

```
mpl_toolkits/mplot3d/proj3d.py:184: RuntimeWarning:
overflow encountered in dot
product = np.dot(axes.M, vec)
```

### Expected outcome

`Poly3DCollection` marks padding for shorter polygons as invalid, but projects the complete backing array before applying that mask. The MRE places a fixed large value only in the entries Matplotlib has marked invalid. Those values should have no effect, but they are passed to the projection matrix and cause an overflow. This deterministically reproduces the projection problem without depending on arbitrary heap contents.

### Additional information

Matplotlib 3.11.0 can project uninitialized padding when `Poly3DCollection` receives polygons with different numbers of vertices.

This occurs with `Axes3D.plot_surface()` when its default downsampling produces non-uniform surface patches. `Poly3DCollection._get_vector()` converts the ragged polygons into a rectangular array:

```python
segments = np.empty((num_faces, max_verts, 3))
for i, face in enumerate(segments3d):
segments[i, :len(face)] = face
```

Unused entries are marked in `_invalid_vertices`, but `do_3d_projection()` projects the complete array before applying that mask:

```python
pfaces = proj3d._scale_proj_transform_vectors(self._faces, self.axes)
```

The uninitialized entries can contain values near the float64 maximum, resulting in:

```text
RuntimeWarning: overflow encountered in dot
```

The behavior is nondeterministic because it depends on heap contents.

This appears related to the vectorized ragged `Poly3DCollection` handling introduced by [#29397](https://github.com/matplotlib/matplotlib/pull/29397), implementing the performance work from [#16659](https://github.com/matplotlib/matplotlib/issues/16659).

## Workaround

Force `plot_surface()` to use its uniform full-resolution path:

```python
ax.plot_surface(x, y, z, rcount=z.shape[0], ccount=z.shape[1])
```

Equivalent:

```python
ax.plot_surface(x, y, z, rstride=1, cstride=1)
```

This produces equally sized quadrilateral faces and avoids the ragged padded representation.

## Suggested Fix

Invalid padding should not participate in projection. Possible approaches include:

- Initialize padding with a safe value rather than `np.empty()`.
- Replace or exclude invalid padding before projection.
- Flatten and project only valid vertices, then reconstruct the polygon boundaries.

Padding with `NaN` was already discussed in [#16659](https://github.com/matplotlib/matplotlib/issues/16659), although the current implementation uses uninitialized storage.

### Operating system

Windows 11, 64-bit

### Matplotlib Version

3.11.0

### Matplotlib Backend

TkAgg / Agg during JPEG output

### Python version

3.14.6

### Jupyter version

_No response_

### Installation

uv

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with Poly3DCollection._get_vector() and do_3d_projection(), then inspect the projection call in mpl_toolkits/mplot3d/proj3d.py. Run the provided ragged-polygon reproduction and the plot_surface() case with default downsampling. Done means invalid padded entries no longer reach projection, and the reproduction completes without the overflow warning.

Written by the indexing model from the issue text.

Assessment

Tech stack
numpy, python
Domain
data-visualization
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.