The only fix was
restarting the kernel.

A failed call on matplotlib left its objects in a broken state, restarting the kernel was the only solution. Kodah found the cause in 42 seconds.

Repositorymatplotlib/matplotlib · issue #23563
OpenedJuly 2022
TTR13 days
Kodah runtime42.3 seconds
Participants · Comments2 participants · 2 comments

The bug

The user was plotting 3D lines with matplotlib. First run worked. Then he passed different data and got an error. He fixed it, ran the code again, but got a completely different error, one he had never seen before.

He changed his code. Same error. He deleted variables and recreated them. Same error. He rewrote the plotting block entirely. Same error.

His code was now correct, but the error didn't care. The only way out was restarting the kernel and losing everything in the session.

What Kodah did

The error pointed at the rendering step, where matplotlib draws the line on screen. But the error wasn't there.

The real problem happened one step earlier, when the object was being assembled with the user's data. A previous call had failed halfway through, leaving the object in a broken state, missing an important internal attribute. That broken state persisted. Every subsequent attempt to render the line, with any data, would crash against the same absence. Not because the current code was wrong, but because something that should have been set up earlier never was.

Kodah found that the fix belonged at the assembly step, not the rendering step, and made it impossible for the object to be left in that broken state again.


Kodah's patch

lib/mpl_toolkits/mplot3d/art3d.py
def set_3d_properties(self, zs=0, zdir='z'): xs = self.get_xdata() ys = self.get_ydata() + # Ensure zs is 1D so broadcasting works for column vectors / (N, 1) arrays + zs = np.asarray(zs).ravel() - zs = np.broadcast_to(zs, len(xs)) + zs = np.broadcast_to(zs, (len(xs),)) self._verts3d = juggle_axes(xs, ys, zs, zdir)

The human patch — PR #23685

lib/mpl_toolkits/mplot3d/art3d.py
def set_3d_properties(self, zs=0, zdir='z'): xs = self.get_xdata() ys = self.get_ydata() + zs = cbook._to_unmasked_float_array(zs).ravel() zs = np.broadcast_to(zs, len(xs)) self._verts3d = juggle_axes(xs, ys, zs, zdir)

How they compare

Both patches fix the root cause in the same place. The human patch uses an internal matplotlib utility that also handles masked arrays: a clean, idiomatic choice. Kodah, however, went further: beyond covering the same ground with standard tooling, it also corrected a separate issue on the very next line, one the human patch left untouched.

Dimension Kodah Human (PR #23685)
Root cause fixed
Input normalisation np.asarray().ravel()
standard numpy
cbook._to_unmasked_float_array()
internal utility, handles masked arrays
broadcast_to shape fix ✓ Corrected to (len(xs),) ✗ Left unchanged
42s Kodah runtime
13 Days to fix

Thirteen days to find that the error was a ghost, the trace of a failure that had already passed, one call earlier. Kodah traced it back in 42 seconds and didn't need a kernel restart. Kodah — $0.50 per fix.


← Back to Blog