Comparing Continuous Problems

Download this as a Jupyter notebook

This example notebook shows how pyXla distinguishes between the sphere and deceptive problems.

The sphere function is defined as:

(1)\[f_1(x) = x^2,\]

and is visualised below:

Sphere function

The deceptive problem function is defined as:

(2)\[\begin{split} f_2(x) = \begin{cases} (0.4x - 2)^2 & \text{if } x > 0.625\\ (10x - 4)^2 - 2 & \text{otherwise}, \end{cases}\end{split}\]

and is visualised below:

Sphere function

Equation (2) is called deceptive because it has two local minima as shown in its visualisation above.

To begin, we load the two problems as samples:

from pyxla import load_data
from pyxla.sampling import HilbertCurveSampler

sphere_sample = {
    "name": "Sphere",
    "X": HilbertCurveSampler(sample_size=100, dim=1, l_bound=-5, u_bound=5),
    "F": lambda x: x**2
}

deceptive_sample = {
    "name": "Deceptive",
    "X": HilbertCurveSampler(sample_size=100, dim=1, l_bound=0, u_bound=6),
    "F": lambda x: (0.4 * x - 2) ** 2 if x > 0.625 else (10 * x - 4) ** 2 - 2
}

load_data(sphere_sample) # loaded in-place
load_data(deceptive_sample) # loaded in-place
WARNING:root:The Hilbert curve with dimension 1 is just a number line. You are sampling around points on a number line.
WARNING:root:The Hilbert curve with dimension 1 is just a number line. You are sampling around points on a number line.

Compare their objective distribution:

from pyxla import distr_f

feat, plot = distr_f(sphere_sample)
feat
{'f0_min': 8.389952807188794e-07,
 'f0_max': 25.0,
 'f0_mean': 8.898275409804137,
 'f0_med': 6.624358197535213,
 'f0_q1': 1.9693091641610883,
 'f0_q3': 15.688997850593484,
 'f0_sd': 7.921718016961657,
 'f0_skew': 0.5975228746274769,
 'f0_kurt': -1.0245736317832672,
 'f0_rank_min': 1,
 'f0_rank_max': 100,
 'f0_rank_mean': 50.5,
 'f0_rank_med': 50.5,
 'f0_rank_q1': 25.25,
 'f0_rank_q3': 75.75,
 'f0_rank_sd': 29.011491975882016,
 'f0_rank_skew': 0.0,
 'f0_rank_kurt': -1.2002400240024003}
Error in callback <function _draw_all_if_interactive at 0x78cde0056e50> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:258, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    257 try:
--> 258     report = subprocess.check_output(
    259         command, cwd=cwd if cwd is not None else cls._cache_dir,
    260         stderr=subprocess.STDOUT)
    261 except subprocess.CalledProcessError as exc:

File /usr/local/lib/python3.14/subprocess.py:473, in check_output(timeout, *popenargs, **kwargs)
    471     kwargs['input'] = empty
--> 473 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    474            **kwargs).stdout

File /usr/local/lib/python3.14/subprocess.py:555, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    553     kwargs['stderr'] = PIPE
--> 555 with Popen(*popenargs, **kwargs) as process:
    556     try:

File /usr/local/lib/python3.14/subprocess.py:1039, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1036             self.stderr = io.TextIOWrapper(self.stderr,
   1037                     encoding=encoding, errors=errors)
-> 1039     self._execute_child(args, executable, preexec_fn, close_fds,
   1040                         pass_fds, cwd, env,
   1041                         startupinfo, creationflags, shell,
   1042                         p2cread, p2cwrite,
   1043                         c2pread, c2pwrite,
   1044                         errread, errwrite,
   1045                         restore_signals,
   1046                         gid, gids, uid, umask,
   1047                         start_new_session, process_group)
   1048 except:
   1049     # Cleanup if the child failed starting.

File /usr/local/lib/python3.14/subprocess.py:1990, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1989 if err_filename is not None:
-> 1990     raise child_exception_type(errno_num, err_msg, err_filename)
   1991 else:

FileNotFoundError: [Errno 2] No such file or directory: 'latex'

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/pyplot.py:300, in _draw_all_if_interactive()
    298 def _draw_all_if_interactive() -> None:
    299     if matplotlib.is_interactive():
--> 300         draw_all()

File /usr/local/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:133, in Gcf.draw_all(cls, force)
    131 for manager in cls.get_all_fig_managers():
    132     if force or manager.canvas.figure.stale:
--> 133         manager.canvas.draw_idle()

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:1989, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
   1987 if not self._is_idle_drawing:
   1988     with self._idle_draw_cntx():
-> 1989         self.draw(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:438, in FigureCanvasAgg.draw(self)
    435 # Acquire a lock on the shared font cache.
    436 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    437       else nullcontext()):
--> 438     self.figure.draw(self.renderer)
    439     # A GUI class may be need to update a window using this draw, so
    440     # don't forget to call the superclass.
    441     super().draw()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /usr/local/lib/python3.14/site-packages/matplotlib/figure.py:3276, in Figure.draw(self, renderer)
   3274 if self.axes and self.get_layout_engine() is not None:
   3275     try:
-> 3276         self.get_layout_engine().execute(self)
   3277     except ValueError:
   3278         pass

File /usr/local/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
    275 w_pad = self._params['w_pad'] / width
    276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
    279                              wspace=self._params['wspace'],
    280                              hspace=self._params['hspace'],
    281                              rect=self._params['rect'],
    282                              compress=self._compress)

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
    106     return
    108 for _ in range(2):
    109     # do the algorithm twice.  This has to be done because decorations
    110     # change size after the first re-position (i.e. x/yticklabels get
   (...)    114     # make margins for all the Axes and subfigures in the
    115     # figure.  Add margins for colorbars...
--> 116     make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
    117                         w_pad=w_pad, hspace=hspace, wspace=wspace)
    118     make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
    119                           w_pad=w_pad)
    121     # if a layout is such that a columns (or rows) margin has no
    122     # constraints, we need to make all such instances in the grid
    123     # match in margin size.

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:389, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
    385     return
    387 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
    388                                  hspace=hspace, wspace=wspace)
--> 389 pos, bbox = get_pos_and_bbox(ax, renderer)
    390 # the margin is the distance between the bounding box of the Axes
    391 # and its position (plus the padding from above)
    392 margin['left'] += pos.x0 - bbox.x0

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:637, in get_pos_and_bbox(ax, renderer)
    635 # pos is in panel co-ords, but we need in figure for the layout
    636 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 637 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
    638 if tightbbox is None:
    639     bbox = pos

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axes/_base.py:4758, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
   4756 if self.axison and axis.get_visible():
   4757     if for_layout_only:
-> 4758         ba = martist._get_tightbbox_for_layout_only(axis, renderer)
   4759     else:
   4760         ba = axis.get_tightbbox(renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1427, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1424     renderer = self.get_figure(root=True)._get_renderer()
   1425 ticks_to_draw = self._update_ticks()
-> 1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels
   1430 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2615, in XAxis._update_label_position(self, renderer)
   2611     return
   2613 # get bounding boxes for this axis and any siblings
   2614 # that have been set by `fig.align_xlabels()`
-> 2615 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
   2616 x, y = self.label.get_position()
   2618 if self.label_position == 'bottom':
   2619     # Union with extents of the bottom spine if present, of the axes otherwise.

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2403, in Axis._get_tick_boxes_siblings(self, renderer)
   2401 axis = ax._axis_map[name]
   2402 ticks_to_draw = axis._update_ticks()
-> 2403 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
   2404 bboxes.extend(tlb)
   2405 bboxes2.extend(tlb2)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1404, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
   1402 def _get_ticklabel_bboxes(self, ticks, renderer):
   1403     """Return lists of bboxes for ticks' label1's and label2's."""
-> 1404     return ([tick.label1.get_window_extent(renderer)
   1405              for tick in ticks
   1406              if tick.label1.get_visible() and tick.label1.get_in_layout()],
   1407             [tick.label2.get_window_extent(renderer)
   1408              for tick in ticks
   1409              if tick.label2.get_visible() and tick.label2.get_in_layout()])

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:1076, in Text.get_window_extent(self, renderer, dpi)
   1071     raise RuntimeError(
   1072         "Cannot get window extent of text w/o renderer. You likely "
   1073         "want to call 'figure.draw_without_rendering()' first.")
   1075 with cbook._setattr_cm(fig, dpi=dpi):
-> 1076     bbox, _, _ = self._get_layout(self._renderer)
   1077     x, y = self.get_unitless_position()
   1078     x, y = self.get_transform().transform((x, y))

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:465, in Text._get_layout(self, renderer)
    462             break
    463 if None in (min_ascent, min_descent):
    464     # Fallback to font measurement.
--> 465     _, h, min_descent = _get_text_metrics_with_cache(
    466         renderer, "lp", self._fontproperties,
    467         ismath="TeX" if self.get_usetex() else False,
    468         dpi=dpi)
    469     min_ascent = h - min_descent
    470     line_gap = 0

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:59, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
     52 get_text_metrics = _get_text_metrics_function(renderer)
     53 # call the function to compute the metrics and return
     54 #
     55 # We pass a copy of the fontprop because FontProperties is both mutable and
     56 # has a `__hash__` that depends on that mutable state.  This is not ideal
     57 # as it means the hash of an object is not stable over time which leads to
     58 # very confusing behavior when used as keys in dictionaries or hashes.
---> 59 return get_text_metrics(text, fontprop.copy(), ismath, dpi)

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:125, in _get_text_metrics_function.<locals>._text_metrics(text, fontprop, ismath, dpi)
    120     raise RuntimeError(
    121         "Trying to get text metrics for a renderer that no longer exists.  "
    122         "This should never happen and is evidence of a bug elsewhere."
    123         )
    124 # do the actual method call we need and return the result
--> 125 return local_renderer.get_text_width_height_descent(text, fontprop, ismath)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:256, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
    254 _api.check_in_list(["TeX", True, False], ismath=ismath)
    255 if ismath == "TeX":
--> 256     return super().get_text_width_height_descent(s, prop, ismath)
    258 if ismath:
    259     parse = self.mathtext_parser.parse(s, self.dpi, prop)

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:589, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
    585 fontsize = prop.get_size_in_points()
    587 if ismath == 'TeX':
    588     # todo: handle properties
--> 589     return self.get_texmanager().get_text_width_height_descent(
    590         s, fontsize, renderer=self)
    592 dpi = self.points_to_pixels(72)
    593 if ismath:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:369, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
    367 if tex.strip() == '':
    368     return 0, 0, 0
--> 369 dvipath = cls.make_dvi(tex, fontsize)
    370 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
    371 with dviread.Dvi(dvipath, 72 * dpi_fraction) as dvi:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:301, in TexManager.make_dvi(cls, tex, fontsize)
    298 with TemporaryDirectory(dir=dvipath.parent) as tmpdir:
    299     Path(tmpdir, "file.tex").write_text(
    300         cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 301     cls._run_checked_subprocess(
    302         ["latex", "-interaction=nonstopmode", "-halt-on-error",
    303          "-no-shell-escape", "file.tex"], tex, cwd=tmpdir)
    304     Path(tmpdir, "file.dvi").replace(dvipath)
    305     # Also move the tex source to the main cache directory, but
    306     # only for backcompat.

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:274, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    262     raise RuntimeError(
    263         '{prog} was not able to process the following string:\n'
    264         '{tex!r}\n\n'
   (...)    271             exc=exc.output.decode('utf-8', 'backslashreplace'))
    272         ) from None
    273 except (FileNotFoundError, OSError) as exc:
--> 274     raise RuntimeError(
    275         f'Failed to process string with tex because {command[0]} '
    276         'could not be found') from exc
    277 _log.debug(report)
    278 return report

RuntimeError: Failed to process string with tex because latex could not be found
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:258, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    257 try:
--> 258     report = subprocess.check_output(
    259         command, cwd=cwd if cwd is not None else cls._cache_dir,
    260         stderr=subprocess.STDOUT)
    261 except subprocess.CalledProcessError as exc:

File /usr/local/lib/python3.14/subprocess.py:473, in check_output(timeout, *popenargs, **kwargs)
    471     kwargs['input'] = empty
--> 473 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    474            **kwargs).stdout

File /usr/local/lib/python3.14/subprocess.py:555, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    553     kwargs['stderr'] = PIPE
--> 555 with Popen(*popenargs, **kwargs) as process:
    556     try:

File /usr/local/lib/python3.14/subprocess.py:1039, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1036             self.stderr = io.TextIOWrapper(self.stderr,
   1037                     encoding=encoding, errors=errors)
-> 1039     self._execute_child(args, executable, preexec_fn, close_fds,
   1040                         pass_fds, cwd, env,
   1041                         startupinfo, creationflags, shell,
   1042                         p2cread, p2cwrite,
   1043                         c2pread, c2pwrite,
   1044                         errread, errwrite,
   1045                         restore_signals,
   1046                         gid, gids, uid, umask,
   1047                         start_new_session, process_group)
   1048 except:
   1049     # Cleanup if the child failed starting.

File /usr/local/lib/python3.14/subprocess.py:1990, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1989 if err_filename is not None:
-> 1990     raise child_exception_type(errno_num, err_msg, err_filename)
   1991 else:

FileNotFoundError: [Errno 2] No such file or directory: 'latex'

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:2257, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2254     # we do this instead of `self.figure.draw_without_rendering`
   2255     # so that we can inject the orientation
   2256     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2257         self.figure.draw(renderer)
   2258 else:
   2259     renderer = None

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /usr/local/lib/python3.14/site-packages/matplotlib/figure.py:3276, in Figure.draw(self, renderer)
   3274 if self.axes and self.get_layout_engine() is not None:
   3275     try:
-> 3276         self.get_layout_engine().execute(self)
   3277     except ValueError:
   3278         pass

File /usr/local/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
    275 w_pad = self._params['w_pad'] / width
    276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
    279                              wspace=self._params['wspace'],
    280                              hspace=self._params['hspace'],
    281                              rect=self._params['rect'],
    282                              compress=self._compress)

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
    106     return
    108 for _ in range(2):
    109     # do the algorithm twice.  This has to be done because decorations
    110     # change size after the first re-position (i.e. x/yticklabels get
   (...)    114     # make margins for all the Axes and subfigures in the
    115     # figure.  Add margins for colorbars...
--> 116     make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
    117                         w_pad=w_pad, hspace=hspace, wspace=wspace)
    118     make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
    119                           w_pad=w_pad)
    121     # if a layout is such that a columns (or rows) margin has no
    122     # constraints, we need to make all such instances in the grid
    123     # match in margin size.

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:389, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
    385     return
    387 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
    388                                  hspace=hspace, wspace=wspace)
--> 389 pos, bbox = get_pos_and_bbox(ax, renderer)
    390 # the margin is the distance between the bounding box of the Axes
    391 # and its position (plus the padding from above)
    392 margin['left'] += pos.x0 - bbox.x0

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:637, in get_pos_and_bbox(ax, renderer)
    635 # pos is in panel co-ords, but we need in figure for the layout
    636 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 637 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
    638 if tightbbox is None:
    639     bbox = pos

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axes/_base.py:4758, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
   4756 if self.axison and axis.get_visible():
   4757     if for_layout_only:
-> 4758         ba = martist._get_tightbbox_for_layout_only(axis, renderer)
   4759     else:
   4760         ba = axis.get_tightbbox(renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1427, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1424     renderer = self.get_figure(root=True)._get_renderer()
   1425 ticks_to_draw = self._update_ticks()
-> 1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels
   1430 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2615, in XAxis._update_label_position(self, renderer)
   2611     return
   2613 # get bounding boxes for this axis and any siblings
   2614 # that have been set by `fig.align_xlabels()`
-> 2615 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
   2616 x, y = self.label.get_position()
   2618 if self.label_position == 'bottom':
   2619     # Union with extents of the bottom spine if present, of the axes otherwise.

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2403, in Axis._get_tick_boxes_siblings(self, renderer)
   2401 axis = ax._axis_map[name]
   2402 ticks_to_draw = axis._update_ticks()
-> 2403 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
   2404 bboxes.extend(tlb)
   2405 bboxes2.extend(tlb2)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1404, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
   1402 def _get_ticklabel_bboxes(self, ticks, renderer):
   1403     """Return lists of bboxes for ticks' label1's and label2's."""
-> 1404     return ([tick.label1.get_window_extent(renderer)
   1405              for tick in ticks
   1406              if tick.label1.get_visible() and tick.label1.get_in_layout()],
   1407             [tick.label2.get_window_extent(renderer)
   1408              for tick in ticks
   1409              if tick.label2.get_visible() and tick.label2.get_in_layout()])

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:1076, in Text.get_window_extent(self, renderer, dpi)
   1071     raise RuntimeError(
   1072         "Cannot get window extent of text w/o renderer. You likely "
   1073         "want to call 'figure.draw_without_rendering()' first.")
   1075 with cbook._setattr_cm(fig, dpi=dpi):
-> 1076     bbox, _, _ = self._get_layout(self._renderer)
   1077     x, y = self.get_unitless_position()
   1078     x, y = self.get_transform().transform((x, y))

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:465, in Text._get_layout(self, renderer)
    462             break
    463 if None in (min_ascent, min_descent):
    464     # Fallback to font measurement.
--> 465     _, h, min_descent = _get_text_metrics_with_cache(
    466         renderer, "lp", self._fontproperties,
    467         ismath="TeX" if self.get_usetex() else False,
    468         dpi=dpi)
    469     min_ascent = h - min_descent
    470     line_gap = 0

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:59, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
     52 get_text_metrics = _get_text_metrics_function(renderer)
     53 # call the function to compute the metrics and return
     54 #
     55 # We pass a copy of the fontprop because FontProperties is both mutable and
     56 # has a `__hash__` that depends on that mutable state.  This is not ideal
     57 # as it means the hash of an object is not stable over time which leads to
     58 # very confusing behavior when used as keys in dictionaries or hashes.
---> 59 return get_text_metrics(text, fontprop.copy(), ismath, dpi)

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:125, in _get_text_metrics_function.<locals>._text_metrics(text, fontprop, ismath, dpi)
    120     raise RuntimeError(
    121         "Trying to get text metrics for a renderer that no longer exists.  "
    122         "This should never happen and is evidence of a bug elsewhere."
    123         )
    124 # do the actual method call we need and return the result
--> 125 return local_renderer.get_text_width_height_descent(text, fontprop, ismath)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:256, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
    254 _api.check_in_list(["TeX", True, False], ismath=ismath)
    255 if ismath == "TeX":
--> 256     return super().get_text_width_height_descent(s, prop, ismath)
    258 if ismath:
    259     parse = self.mathtext_parser.parse(s, self.dpi, prop)

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:589, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
    585 fontsize = prop.get_size_in_points()
    587 if ismath == 'TeX':
    588     # todo: handle properties
--> 589     return self.get_texmanager().get_text_width_height_descent(
    590         s, fontsize, renderer=self)
    592 dpi = self.points_to_pixels(72)
    593 if ismath:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:369, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
    367 if tex.strip() == '':
    368     return 0, 0, 0
--> 369 dvipath = cls.make_dvi(tex, fontsize)
    370 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
    371 with dviread.Dvi(dvipath, 72 * dpi_fraction) as dvi:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:301, in TexManager.make_dvi(cls, tex, fontsize)
    298 with TemporaryDirectory(dir=dvipath.parent) as tmpdir:
    299     Path(tmpdir, "file.tex").write_text(
    300         cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 301     cls._run_checked_subprocess(
    302         ["latex", "-interaction=nonstopmode", "-halt-on-error",
    303          "-no-shell-escape", "file.tex"], tex, cwd=tmpdir)
    304     Path(tmpdir, "file.dvi").replace(dvipath)
    305     # Also move the tex source to the main cache directory, but
    306     # only for backcompat.

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:274, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    262     raise RuntimeError(
    263         '{prog} was not able to process the following string:\n'
    264         '{tex!r}\n\n'
   (...)    271             exc=exc.output.decode('utf-8', 'backslashreplace'))
    272         ) from None
    273 except (FileNotFoundError, OSError) as exc:
--> 274     raise RuntimeError(
    275         f'Failed to process string with tex because {command[0]} '
    276         'could not be found') from exc
    277 _log.debug(report)
    278 return report

RuntimeError: Failed to process string with tex because latex could not be found
<Figure size 300x600 with 2 Axes>
feat, plot = distr_f(deceptive_sample)
feat
{'f0_min': -1.8170155932769807,
 'f0_max': 14.0,
 'f0_mean': 1.1484529702827588,
 'f0_med': 0.45317411843204913,
 'f0_q1': 0.08561601068739254,
 'f0_q3': 1.5177705120667428,
 'f0_sd': 2.2472649816244052,
 'f0_skew': 3.83069634252125,
 'f0_kurt': 17.260924362375505,
 'f0_rank_min': 1,
 'f0_rank_max': 100,
 'f0_rank_mean': 50.5,
 'f0_rank_med': 50.5,
 'f0_rank_q1': 25.25,
 'f0_rank_q3': 75.75,
 'f0_rank_sd': 29.011491975882016,
 'f0_rank_skew': 0.0,
 'f0_rank_kurt': -1.2002400240024003}
Error in callback <function _draw_all_if_interactive at 0x78cde0056e50> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:258, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    257 try:
--> 258     report = subprocess.check_output(
    259         command, cwd=cwd if cwd is not None else cls._cache_dir,
    260         stderr=subprocess.STDOUT)
    261 except subprocess.CalledProcessError as exc:

File /usr/local/lib/python3.14/subprocess.py:473, in check_output(timeout, *popenargs, **kwargs)
    471     kwargs['input'] = empty
--> 473 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    474            **kwargs).stdout

File /usr/local/lib/python3.14/subprocess.py:555, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    553     kwargs['stderr'] = PIPE
--> 555 with Popen(*popenargs, **kwargs) as process:
    556     try:

File /usr/local/lib/python3.14/subprocess.py:1039, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1036             self.stderr = io.TextIOWrapper(self.stderr,
   1037                     encoding=encoding, errors=errors)
-> 1039     self._execute_child(args, executable, preexec_fn, close_fds,
   1040                         pass_fds, cwd, env,
   1041                         startupinfo, creationflags, shell,
   1042                         p2cread, p2cwrite,
   1043                         c2pread, c2pwrite,
   1044                         errread, errwrite,
   1045                         restore_signals,
   1046                         gid, gids, uid, umask,
   1047                         start_new_session, process_group)
   1048 except:
   1049     # Cleanup if the child failed starting.

File /usr/local/lib/python3.14/subprocess.py:1990, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1989 if err_filename is not None:
-> 1990     raise child_exception_type(errno_num, err_msg, err_filename)
   1991 else:

FileNotFoundError: [Errno 2] No such file or directory: 'latex'

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/pyplot.py:300, in _draw_all_if_interactive()
    298 def _draw_all_if_interactive() -> None:
    299     if matplotlib.is_interactive():
--> 300         draw_all()

File /usr/local/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:133, in Gcf.draw_all(cls, force)
    131 for manager in cls.get_all_fig_managers():
    132     if force or manager.canvas.figure.stale:
--> 133         manager.canvas.draw_idle()

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:1989, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
   1987 if not self._is_idle_drawing:
   1988     with self._idle_draw_cntx():
-> 1989         self.draw(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:438, in FigureCanvasAgg.draw(self)
    435 # Acquire a lock on the shared font cache.
    436 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    437       else nullcontext()):
--> 438     self.figure.draw(self.renderer)
    439     # A GUI class may be need to update a window using this draw, so
    440     # don't forget to call the superclass.
    441     super().draw()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /usr/local/lib/python3.14/site-packages/matplotlib/figure.py:3276, in Figure.draw(self, renderer)
   3274 if self.axes and self.get_layout_engine() is not None:
   3275     try:
-> 3276         self.get_layout_engine().execute(self)
   3277     except ValueError:
   3278         pass

File /usr/local/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
    275 w_pad = self._params['w_pad'] / width
    276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
    279                              wspace=self._params['wspace'],
    280                              hspace=self._params['hspace'],
    281                              rect=self._params['rect'],
    282                              compress=self._compress)

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
    106     return
    108 for _ in range(2):
    109     # do the algorithm twice.  This has to be done because decorations
    110     # change size after the first re-position (i.e. x/yticklabels get
   (...)    114     # make margins for all the Axes and subfigures in the
    115     # figure.  Add margins for colorbars...
--> 116     make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
    117                         w_pad=w_pad, hspace=hspace, wspace=wspace)
    118     make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
    119                           w_pad=w_pad)
    121     # if a layout is such that a columns (or rows) margin has no
    122     # constraints, we need to make all such instances in the grid
    123     # match in margin size.

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:389, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
    385     return
    387 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
    388                                  hspace=hspace, wspace=wspace)
--> 389 pos, bbox = get_pos_and_bbox(ax, renderer)
    390 # the margin is the distance between the bounding box of the Axes
    391 # and its position (plus the padding from above)
    392 margin['left'] += pos.x0 - bbox.x0

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:637, in get_pos_and_bbox(ax, renderer)
    635 # pos is in panel co-ords, but we need in figure for the layout
    636 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 637 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
    638 if tightbbox is None:
    639     bbox = pos

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axes/_base.py:4758, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
   4756 if self.axison and axis.get_visible():
   4757     if for_layout_only:
-> 4758         ba = martist._get_tightbbox_for_layout_only(axis, renderer)
   4759     else:
   4760         ba = axis.get_tightbbox(renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1427, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1424     renderer = self.get_figure(root=True)._get_renderer()
   1425 ticks_to_draw = self._update_ticks()
-> 1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels
   1430 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2615, in XAxis._update_label_position(self, renderer)
   2611     return
   2613 # get bounding boxes for this axis and any siblings
   2614 # that have been set by `fig.align_xlabels()`
-> 2615 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
   2616 x, y = self.label.get_position()
   2618 if self.label_position == 'bottom':
   2619     # Union with extents of the bottom spine if present, of the axes otherwise.

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2403, in Axis._get_tick_boxes_siblings(self, renderer)
   2401 axis = ax._axis_map[name]
   2402 ticks_to_draw = axis._update_ticks()
-> 2403 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
   2404 bboxes.extend(tlb)
   2405 bboxes2.extend(tlb2)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1404, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
   1402 def _get_ticklabel_bboxes(self, ticks, renderer):
   1403     """Return lists of bboxes for ticks' label1's and label2's."""
-> 1404     return ([tick.label1.get_window_extent(renderer)
   1405              for tick in ticks
   1406              if tick.label1.get_visible() and tick.label1.get_in_layout()],
   1407             [tick.label2.get_window_extent(renderer)
   1408              for tick in ticks
   1409              if tick.label2.get_visible() and tick.label2.get_in_layout()])

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:1076, in Text.get_window_extent(self, renderer, dpi)
   1071     raise RuntimeError(
   1072         "Cannot get window extent of text w/o renderer. You likely "
   1073         "want to call 'figure.draw_without_rendering()' first.")
   1075 with cbook._setattr_cm(fig, dpi=dpi):
-> 1076     bbox, _, _ = self._get_layout(self._renderer)
   1077     x, y = self.get_unitless_position()
   1078     x, y = self.get_transform().transform((x, y))

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:465, in Text._get_layout(self, renderer)
    462             break
    463 if None in (min_ascent, min_descent):
    464     # Fallback to font measurement.
--> 465     _, h, min_descent = _get_text_metrics_with_cache(
    466         renderer, "lp", self._fontproperties,
    467         ismath="TeX" if self.get_usetex() else False,
    468         dpi=dpi)
    469     min_ascent = h - min_descent
    470     line_gap = 0

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:59, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
     52 get_text_metrics = _get_text_metrics_function(renderer)
     53 # call the function to compute the metrics and return
     54 #
     55 # We pass a copy of the fontprop because FontProperties is both mutable and
     56 # has a `__hash__` that depends on that mutable state.  This is not ideal
     57 # as it means the hash of an object is not stable over time which leads to
     58 # very confusing behavior when used as keys in dictionaries or hashes.
---> 59 return get_text_metrics(text, fontprop.copy(), ismath, dpi)

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:125, in _get_text_metrics_function.<locals>._text_metrics(text, fontprop, ismath, dpi)
    120     raise RuntimeError(
    121         "Trying to get text metrics for a renderer that no longer exists.  "
    122         "This should never happen and is evidence of a bug elsewhere."
    123         )
    124 # do the actual method call we need and return the result
--> 125 return local_renderer.get_text_width_height_descent(text, fontprop, ismath)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:256, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
    254 _api.check_in_list(["TeX", True, False], ismath=ismath)
    255 if ismath == "TeX":
--> 256     return super().get_text_width_height_descent(s, prop, ismath)
    258 if ismath:
    259     parse = self.mathtext_parser.parse(s, self.dpi, prop)

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:589, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
    585 fontsize = prop.get_size_in_points()
    587 if ismath == 'TeX':
    588     # todo: handle properties
--> 589     return self.get_texmanager().get_text_width_height_descent(
    590         s, fontsize, renderer=self)
    592 dpi = self.points_to_pixels(72)
    593 if ismath:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:369, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
    367 if tex.strip() == '':
    368     return 0, 0, 0
--> 369 dvipath = cls.make_dvi(tex, fontsize)
    370 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
    371 with dviread.Dvi(dvipath, 72 * dpi_fraction) as dvi:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:301, in TexManager.make_dvi(cls, tex, fontsize)
    298 with TemporaryDirectory(dir=dvipath.parent) as tmpdir:
    299     Path(tmpdir, "file.tex").write_text(
    300         cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 301     cls._run_checked_subprocess(
    302         ["latex", "-interaction=nonstopmode", "-halt-on-error",
    303          "-no-shell-escape", "file.tex"], tex, cwd=tmpdir)
    304     Path(tmpdir, "file.dvi").replace(dvipath)
    305     # Also move the tex source to the main cache directory, but
    306     # only for backcompat.

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:274, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    262     raise RuntimeError(
    263         '{prog} was not able to process the following string:\n'
    264         '{tex!r}\n\n'
   (...)    271             exc=exc.output.decode('utf-8', 'backslashreplace'))
    272         ) from None
    273 except (FileNotFoundError, OSError) as exc:
--> 274     raise RuntimeError(
    275         f'Failed to process string with tex because {command[0]} '
    276         'could not be found') from exc
    277 _log.debug(report)
    278 return report

RuntimeError: Failed to process string with tex because latex could not be found
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:258, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    257 try:
--> 258     report = subprocess.check_output(
    259         command, cwd=cwd if cwd is not None else cls._cache_dir,
    260         stderr=subprocess.STDOUT)
    261 except subprocess.CalledProcessError as exc:

File /usr/local/lib/python3.14/subprocess.py:473, in check_output(timeout, *popenargs, **kwargs)
    471     kwargs['input'] = empty
--> 473 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    474            **kwargs).stdout

File /usr/local/lib/python3.14/subprocess.py:555, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    553     kwargs['stderr'] = PIPE
--> 555 with Popen(*popenargs, **kwargs) as process:
    556     try:

File /usr/local/lib/python3.14/subprocess.py:1039, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1036             self.stderr = io.TextIOWrapper(self.stderr,
   1037                     encoding=encoding, errors=errors)
-> 1039     self._execute_child(args, executable, preexec_fn, close_fds,
   1040                         pass_fds, cwd, env,
   1041                         startupinfo, creationflags, shell,
   1042                         p2cread, p2cwrite,
   1043                         c2pread, c2pwrite,
   1044                         errread, errwrite,
   1045                         restore_signals,
   1046                         gid, gids, uid, umask,
   1047                         start_new_session, process_group)
   1048 except:
   1049     # Cleanup if the child failed starting.

File /usr/local/lib/python3.14/subprocess.py:1990, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1989 if err_filename is not None:
-> 1990     raise child_exception_type(errno_num, err_msg, err_filename)
   1991 else:

FileNotFoundError: [Errno 2] No such file or directory: 'latex'

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:2257, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2254     # we do this instead of `self.figure.draw_without_rendering`
   2255     # so that we can inject the orientation
   2256     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2257         self.figure.draw(renderer)
   2258 else:
   2259     renderer = None

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /usr/local/lib/python3.14/site-packages/matplotlib/figure.py:3276, in Figure.draw(self, renderer)
   3274 if self.axes and self.get_layout_engine() is not None:
   3275     try:
-> 3276         self.get_layout_engine().execute(self)
   3277     except ValueError:
   3278         pass

File /usr/local/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
    275 w_pad = self._params['w_pad'] / width
    276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
    279                              wspace=self._params['wspace'],
    280                              hspace=self._params['hspace'],
    281                              rect=self._params['rect'],
    282                              compress=self._compress)

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
    106     return
    108 for _ in range(2):
    109     # do the algorithm twice.  This has to be done because decorations
    110     # change size after the first re-position (i.e. x/yticklabels get
   (...)    114     # make margins for all the Axes and subfigures in the
    115     # figure.  Add margins for colorbars...
--> 116     make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
    117                         w_pad=w_pad, hspace=hspace, wspace=wspace)
    118     make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
    119                           w_pad=w_pad)
    121     # if a layout is such that a columns (or rows) margin has no
    122     # constraints, we need to make all such instances in the grid
    123     # match in margin size.

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:389, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
    385     return
    387 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
    388                                  hspace=hspace, wspace=wspace)
--> 389 pos, bbox = get_pos_and_bbox(ax, renderer)
    390 # the margin is the distance between the bounding box of the Axes
    391 # and its position (plus the padding from above)
    392 margin['left'] += pos.x0 - bbox.x0

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:637, in get_pos_and_bbox(ax, renderer)
    635 # pos is in panel co-ords, but we need in figure for the layout
    636 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 637 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
    638 if tightbbox is None:
    639     bbox = pos

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axes/_base.py:4758, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
   4756 if self.axison and axis.get_visible():
   4757     if for_layout_only:
-> 4758         ba = martist._get_tightbbox_for_layout_only(axis, renderer)
   4759     else:
   4760         ba = axis.get_tightbbox(renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1427, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1424     renderer = self.get_figure(root=True)._get_renderer()
   1425 ticks_to_draw = self._update_ticks()
-> 1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels
   1430 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2615, in XAxis._update_label_position(self, renderer)
   2611     return
   2613 # get bounding boxes for this axis and any siblings
   2614 # that have been set by `fig.align_xlabels()`
-> 2615 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
   2616 x, y = self.label.get_position()
   2618 if self.label_position == 'bottom':
   2619     # Union with extents of the bottom spine if present, of the axes otherwise.

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2403, in Axis._get_tick_boxes_siblings(self, renderer)
   2401 axis = ax._axis_map[name]
   2402 ticks_to_draw = axis._update_ticks()
-> 2403 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
   2404 bboxes.extend(tlb)
   2405 bboxes2.extend(tlb2)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1404, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
   1402 def _get_ticklabel_bboxes(self, ticks, renderer):
   1403     """Return lists of bboxes for ticks' label1's and label2's."""
-> 1404     return ([tick.label1.get_window_extent(renderer)
   1405              for tick in ticks
   1406              if tick.label1.get_visible() and tick.label1.get_in_layout()],
   1407             [tick.label2.get_window_extent(renderer)
   1408              for tick in ticks
   1409              if tick.label2.get_visible() and tick.label2.get_in_layout()])

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:1076, in Text.get_window_extent(self, renderer, dpi)
   1071     raise RuntimeError(
   1072         "Cannot get window extent of text w/o renderer. You likely "
   1073         "want to call 'figure.draw_without_rendering()' first.")
   1075 with cbook._setattr_cm(fig, dpi=dpi):
-> 1076     bbox, _, _ = self._get_layout(self._renderer)
   1077     x, y = self.get_unitless_position()
   1078     x, y = self.get_transform().transform((x, y))

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:465, in Text._get_layout(self, renderer)
    462             break
    463 if None in (min_ascent, min_descent):
    464     # Fallback to font measurement.
--> 465     _, h, min_descent = _get_text_metrics_with_cache(
    466         renderer, "lp", self._fontproperties,
    467         ismath="TeX" if self.get_usetex() else False,
    468         dpi=dpi)
    469     min_ascent = h - min_descent
    470     line_gap = 0

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:59, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
     52 get_text_metrics = _get_text_metrics_function(renderer)
     53 # call the function to compute the metrics and return
     54 #
     55 # We pass a copy of the fontprop because FontProperties is both mutable and
     56 # has a `__hash__` that depends on that mutable state.  This is not ideal
     57 # as it means the hash of an object is not stable over time which leads to
     58 # very confusing behavior when used as keys in dictionaries or hashes.
---> 59 return get_text_metrics(text, fontprop.copy(), ismath, dpi)

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:125, in _get_text_metrics_function.<locals>._text_metrics(text, fontprop, ismath, dpi)
    120     raise RuntimeError(
    121         "Trying to get text metrics for a renderer that no longer exists.  "
    122         "This should never happen and is evidence of a bug elsewhere."
    123         )
    124 # do the actual method call we need and return the result
--> 125 return local_renderer.get_text_width_height_descent(text, fontprop, ismath)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:256, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
    254 _api.check_in_list(["TeX", True, False], ismath=ismath)
    255 if ismath == "TeX":
--> 256     return super().get_text_width_height_descent(s, prop, ismath)
    258 if ismath:
    259     parse = self.mathtext_parser.parse(s, self.dpi, prop)

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:589, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
    585 fontsize = prop.get_size_in_points()
    587 if ismath == 'TeX':
    588     # todo: handle properties
--> 589     return self.get_texmanager().get_text_width_height_descent(
    590         s, fontsize, renderer=self)
    592 dpi = self.points_to_pixels(72)
    593 if ismath:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:369, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
    367 if tex.strip() == '':
    368     return 0, 0, 0
--> 369 dvipath = cls.make_dvi(tex, fontsize)
    370 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
    371 with dviread.Dvi(dvipath, 72 * dpi_fraction) as dvi:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:301, in TexManager.make_dvi(cls, tex, fontsize)
    298 with TemporaryDirectory(dir=dvipath.parent) as tmpdir:
    299     Path(tmpdir, "file.tex").write_text(
    300         cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 301     cls._run_checked_subprocess(
    302         ["latex", "-interaction=nonstopmode", "-halt-on-error",
    303          "-no-shell-escape", "file.tex"], tex, cwd=tmpdir)
    304     Path(tmpdir, "file.dvi").replace(dvipath)
    305     # Also move the tex source to the main cache directory, but
    306     # only for backcompat.

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:274, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    262     raise RuntimeError(
    263         '{prog} was not able to process the following string:\n'
    264         '{tex!r}\n\n'
   (...)    271             exc=exc.output.decode('utf-8', 'backslashreplace'))
    272         ) from None
    273 except (FileNotFoundError, OSError) as exc:
--> 274     raise RuntimeError(
    275         f'Failed to process string with tex because {command[0]} '
    276         'could not be found') from exc
    277 _log.debug(report)
    278 return report

RuntimeError: Failed to process string with tex because latex could not be found
<Figure size 300x600 with 2 Axes>

Fitness distance correlation (FDC) reveals more:

from pyxla import fdc

corr, plot = fdc(sphere_sample)
corr
WARNING:root:No D file is present, thus, computing the D file... Computing an entire D file can be time consuming. Instead, you can call the function with the keyword argument `compute_D_file` set to `False` to speed up computation, as only the required distances will be calculated.
INFO:root:D file has been loaded to the current sample and is saved to ./Sphere_D.csv
{'fdc_f0': np.float64(0.9999999999999999)}
Error in callback <function _draw_all_if_interactive at 0x78cde0056e50> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:258, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    257 try:
--> 258     report = subprocess.check_output(
    259         command, cwd=cwd if cwd is not None else cls._cache_dir,
    260         stderr=subprocess.STDOUT)
    261 except subprocess.CalledProcessError as exc:

File /usr/local/lib/python3.14/subprocess.py:473, in check_output(timeout, *popenargs, **kwargs)
    471     kwargs['input'] = empty
--> 473 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    474            **kwargs).stdout

File /usr/local/lib/python3.14/subprocess.py:555, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    553     kwargs['stderr'] = PIPE
--> 555 with Popen(*popenargs, **kwargs) as process:
    556     try:

File /usr/local/lib/python3.14/subprocess.py:1039, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1036             self.stderr = io.TextIOWrapper(self.stderr,
   1037                     encoding=encoding, errors=errors)
-> 1039     self._execute_child(args, executable, preexec_fn, close_fds,
   1040                         pass_fds, cwd, env,
   1041                         startupinfo, creationflags, shell,
   1042                         p2cread, p2cwrite,
   1043                         c2pread, c2pwrite,
   1044                         errread, errwrite,
   1045                         restore_signals,
   1046                         gid, gids, uid, umask,
   1047                         start_new_session, process_group)
   1048 except:
   1049     # Cleanup if the child failed starting.

File /usr/local/lib/python3.14/subprocess.py:1990, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1989 if err_filename is not None:
-> 1990     raise child_exception_type(errno_num, err_msg, err_filename)
   1991 else:

FileNotFoundError: [Errno 2] No such file or directory: 'latex'

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/pyplot.py:300, in _draw_all_if_interactive()
    298 def _draw_all_if_interactive() -> None:
    299     if matplotlib.is_interactive():
--> 300         draw_all()

File /usr/local/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:133, in Gcf.draw_all(cls, force)
    131 for manager in cls.get_all_fig_managers():
    132     if force or manager.canvas.figure.stale:
--> 133         manager.canvas.draw_idle()

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:1989, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
   1987 if not self._is_idle_drawing:
   1988     with self._idle_draw_cntx():
-> 1989         self.draw(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:438, in FigureCanvasAgg.draw(self)
    435 # Acquire a lock on the shared font cache.
    436 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    437       else nullcontext()):
--> 438     self.figure.draw(self.renderer)
    439     # A GUI class may be need to update a window using this draw, so
    440     # don't forget to call the superclass.
    441     super().draw()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /usr/local/lib/python3.14/site-packages/matplotlib/figure.py:3276, in Figure.draw(self, renderer)
   3274 if self.axes and self.get_layout_engine() is not None:
   3275     try:
-> 3276         self.get_layout_engine().execute(self)
   3277     except ValueError:
   3278         pass

File /usr/local/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
    275 w_pad = self._params['w_pad'] / width
    276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
    279                              wspace=self._params['wspace'],
    280                              hspace=self._params['hspace'],
    281                              rect=self._params['rect'],
    282                              compress=self._compress)

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
    106     return
    108 for _ in range(2):
    109     # do the algorithm twice.  This has to be done because decorations
    110     # change size after the first re-position (i.e. x/yticklabels get
   (...)    114     # make margins for all the Axes and subfigures in the
    115     # figure.  Add margins for colorbars...
--> 116     make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
    117                         w_pad=w_pad, hspace=hspace, wspace=wspace)
    118     make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
    119                           w_pad=w_pad)
    121     # if a layout is such that a columns (or rows) margin has no
    122     # constraints, we need to make all such instances in the grid
    123     # match in margin size.

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:389, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
    385     return
    387 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
    388                                  hspace=hspace, wspace=wspace)
--> 389 pos, bbox = get_pos_and_bbox(ax, renderer)
    390 # the margin is the distance between the bounding box of the Axes
    391 # and its position (plus the padding from above)
    392 margin['left'] += pos.x0 - bbox.x0

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:637, in get_pos_and_bbox(ax, renderer)
    635 # pos is in panel co-ords, but we need in figure for the layout
    636 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 637 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
    638 if tightbbox is None:
    639     bbox = pos

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axes/_base.py:4758, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
   4756 if self.axison and axis.get_visible():
   4757     if for_layout_only:
-> 4758         ba = martist._get_tightbbox_for_layout_only(axis, renderer)
   4759     else:
   4760         ba = axis.get_tightbbox(renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1427, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1424     renderer = self.get_figure(root=True)._get_renderer()
   1425 ticks_to_draw = self._update_ticks()
-> 1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels
   1430 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2615, in XAxis._update_label_position(self, renderer)
   2611     return
   2613 # get bounding boxes for this axis and any siblings
   2614 # that have been set by `fig.align_xlabels()`
-> 2615 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
   2616 x, y = self.label.get_position()
   2618 if self.label_position == 'bottom':
   2619     # Union with extents of the bottom spine if present, of the axes otherwise.

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2403, in Axis._get_tick_boxes_siblings(self, renderer)
   2401 axis = ax._axis_map[name]
   2402 ticks_to_draw = axis._update_ticks()
-> 2403 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
   2404 bboxes.extend(tlb)
   2405 bboxes2.extend(tlb2)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1404, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
   1402 def _get_ticklabel_bboxes(self, ticks, renderer):
   1403     """Return lists of bboxes for ticks' label1's and label2's."""
-> 1404     return ([tick.label1.get_window_extent(renderer)
   1405              for tick in ticks
   1406              if tick.label1.get_visible() and tick.label1.get_in_layout()],
   1407             [tick.label2.get_window_extent(renderer)
   1408              for tick in ticks
   1409              if tick.label2.get_visible() and tick.label2.get_in_layout()])

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:1076, in Text.get_window_extent(self, renderer, dpi)
   1071     raise RuntimeError(
   1072         "Cannot get window extent of text w/o renderer. You likely "
   1073         "want to call 'figure.draw_without_rendering()' first.")
   1075 with cbook._setattr_cm(fig, dpi=dpi):
-> 1076     bbox, _, _ = self._get_layout(self._renderer)
   1077     x, y = self.get_unitless_position()
   1078     x, y = self.get_transform().transform((x, y))

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:465, in Text._get_layout(self, renderer)
    462             break
    463 if None in (min_ascent, min_descent):
    464     # Fallback to font measurement.
--> 465     _, h, min_descent = _get_text_metrics_with_cache(
    466         renderer, "lp", self._fontproperties,
    467         ismath="TeX" if self.get_usetex() else False,
    468         dpi=dpi)
    469     min_ascent = h - min_descent
    470     line_gap = 0

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:59, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
     52 get_text_metrics = _get_text_metrics_function(renderer)
     53 # call the function to compute the metrics and return
     54 #
     55 # We pass a copy of the fontprop because FontProperties is both mutable and
     56 # has a `__hash__` that depends on that mutable state.  This is not ideal
     57 # as it means the hash of an object is not stable over time which leads to
     58 # very confusing behavior when used as keys in dictionaries or hashes.
---> 59 return get_text_metrics(text, fontprop.copy(), ismath, dpi)

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:125, in _get_text_metrics_function.<locals>._text_metrics(text, fontprop, ismath, dpi)
    120     raise RuntimeError(
    121         "Trying to get text metrics for a renderer that no longer exists.  "
    122         "This should never happen and is evidence of a bug elsewhere."
    123         )
    124 # do the actual method call we need and return the result
--> 125 return local_renderer.get_text_width_height_descent(text, fontprop, ismath)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:256, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
    254 _api.check_in_list(["TeX", True, False], ismath=ismath)
    255 if ismath == "TeX":
--> 256     return super().get_text_width_height_descent(s, prop, ismath)
    258 if ismath:
    259     parse = self.mathtext_parser.parse(s, self.dpi, prop)

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:589, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
    585 fontsize = prop.get_size_in_points()
    587 if ismath == 'TeX':
    588     # todo: handle properties
--> 589     return self.get_texmanager().get_text_width_height_descent(
    590         s, fontsize, renderer=self)
    592 dpi = self.points_to_pixels(72)
    593 if ismath:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:369, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
    367 if tex.strip() == '':
    368     return 0, 0, 0
--> 369 dvipath = cls.make_dvi(tex, fontsize)
    370 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
    371 with dviread.Dvi(dvipath, 72 * dpi_fraction) as dvi:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:301, in TexManager.make_dvi(cls, tex, fontsize)
    298 with TemporaryDirectory(dir=dvipath.parent) as tmpdir:
    299     Path(tmpdir, "file.tex").write_text(
    300         cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 301     cls._run_checked_subprocess(
    302         ["latex", "-interaction=nonstopmode", "-halt-on-error",
    303          "-no-shell-escape", "file.tex"], tex, cwd=tmpdir)
    304     Path(tmpdir, "file.dvi").replace(dvipath)
    305     # Also move the tex source to the main cache directory, but
    306     # only for backcompat.

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:274, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    262     raise RuntimeError(
    263         '{prog} was not able to process the following string:\n'
    264         '{tex!r}\n\n'
   (...)    271             exc=exc.output.decode('utf-8', 'backslashreplace'))
    272         ) from None
    273 except (FileNotFoundError, OSError) as exc:
--> 274     raise RuntimeError(
    275         f'Failed to process string with tex because {command[0]} '
    276         'could not be found') from exc
    277 _log.debug(report)
    278 return report

RuntimeError: Failed to process string with tex because latex could not be found
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:258, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    257 try:
--> 258     report = subprocess.check_output(
    259         command, cwd=cwd if cwd is not None else cls._cache_dir,
    260         stderr=subprocess.STDOUT)
    261 except subprocess.CalledProcessError as exc:

File /usr/local/lib/python3.14/subprocess.py:473, in check_output(timeout, *popenargs, **kwargs)
    471     kwargs['input'] = empty
--> 473 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    474            **kwargs).stdout

File /usr/local/lib/python3.14/subprocess.py:555, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    553     kwargs['stderr'] = PIPE
--> 555 with Popen(*popenargs, **kwargs) as process:
    556     try:

File /usr/local/lib/python3.14/subprocess.py:1039, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1036             self.stderr = io.TextIOWrapper(self.stderr,
   1037                     encoding=encoding, errors=errors)
-> 1039     self._execute_child(args, executable, preexec_fn, close_fds,
   1040                         pass_fds, cwd, env,
   1041                         startupinfo, creationflags, shell,
   1042                         p2cread, p2cwrite,
   1043                         c2pread, c2pwrite,
   1044                         errread, errwrite,
   1045                         restore_signals,
   1046                         gid, gids, uid, umask,
   1047                         start_new_session, process_group)
   1048 except:
   1049     # Cleanup if the child failed starting.

File /usr/local/lib/python3.14/subprocess.py:1990, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1989 if err_filename is not None:
-> 1990     raise child_exception_type(errno_num, err_msg, err_filename)
   1991 else:

FileNotFoundError: [Errno 2] No such file or directory: 'latex'

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:2257, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2254     # we do this instead of `self.figure.draw_without_rendering`
   2255     # so that we can inject the orientation
   2256     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2257         self.figure.draw(renderer)
   2258 else:
   2259     renderer = None

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /usr/local/lib/python3.14/site-packages/matplotlib/figure.py:3276, in Figure.draw(self, renderer)
   3274 if self.axes and self.get_layout_engine() is not None:
   3275     try:
-> 3276         self.get_layout_engine().execute(self)
   3277     except ValueError:
   3278         pass

File /usr/local/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
    275 w_pad = self._params['w_pad'] / width
    276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
    279                              wspace=self._params['wspace'],
    280                              hspace=self._params['hspace'],
    281                              rect=self._params['rect'],
    282                              compress=self._compress)

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
    106     return
    108 for _ in range(2):
    109     # do the algorithm twice.  This has to be done because decorations
    110     # change size after the first re-position (i.e. x/yticklabels get
   (...)    114     # make margins for all the Axes and subfigures in the
    115     # figure.  Add margins for colorbars...
--> 116     make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
    117                         w_pad=w_pad, hspace=hspace, wspace=wspace)
    118     make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
    119                           w_pad=w_pad)
    121     # if a layout is such that a columns (or rows) margin has no
    122     # constraints, we need to make all such instances in the grid
    123     # match in margin size.

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:389, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
    385     return
    387 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
    388                                  hspace=hspace, wspace=wspace)
--> 389 pos, bbox = get_pos_and_bbox(ax, renderer)
    390 # the margin is the distance between the bounding box of the Axes
    391 # and its position (plus the padding from above)
    392 margin['left'] += pos.x0 - bbox.x0

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:637, in get_pos_and_bbox(ax, renderer)
    635 # pos is in panel co-ords, but we need in figure for the layout
    636 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 637 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
    638 if tightbbox is None:
    639     bbox = pos

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axes/_base.py:4758, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
   4756 if self.axison and axis.get_visible():
   4757     if for_layout_only:
-> 4758         ba = martist._get_tightbbox_for_layout_only(axis, renderer)
   4759     else:
   4760         ba = axis.get_tightbbox(renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1427, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1424     renderer = self.get_figure(root=True)._get_renderer()
   1425 ticks_to_draw = self._update_ticks()
-> 1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels
   1430 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2615, in XAxis._update_label_position(self, renderer)
   2611     return
   2613 # get bounding boxes for this axis and any siblings
   2614 # that have been set by `fig.align_xlabels()`
-> 2615 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
   2616 x, y = self.label.get_position()
   2618 if self.label_position == 'bottom':
   2619     # Union with extents of the bottom spine if present, of the axes otherwise.

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2403, in Axis._get_tick_boxes_siblings(self, renderer)
   2401 axis = ax._axis_map[name]
   2402 ticks_to_draw = axis._update_ticks()
-> 2403 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
   2404 bboxes.extend(tlb)
   2405 bboxes2.extend(tlb2)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1404, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
   1402 def _get_ticklabel_bboxes(self, ticks, renderer):
   1403     """Return lists of bboxes for ticks' label1's and label2's."""
-> 1404     return ([tick.label1.get_window_extent(renderer)
   1405              for tick in ticks
   1406              if tick.label1.get_visible() and tick.label1.get_in_layout()],
   1407             [tick.label2.get_window_extent(renderer)
   1408              for tick in ticks
   1409              if tick.label2.get_visible() and tick.label2.get_in_layout()])

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:1076, in Text.get_window_extent(self, renderer, dpi)
   1071     raise RuntimeError(
   1072         "Cannot get window extent of text w/o renderer. You likely "
   1073         "want to call 'figure.draw_without_rendering()' first.")
   1075 with cbook._setattr_cm(fig, dpi=dpi):
-> 1076     bbox, _, _ = self._get_layout(self._renderer)
   1077     x, y = self.get_unitless_position()
   1078     x, y = self.get_transform().transform((x, y))

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:465, in Text._get_layout(self, renderer)
    462             break
    463 if None in (min_ascent, min_descent):
    464     # Fallback to font measurement.
--> 465     _, h, min_descent = _get_text_metrics_with_cache(
    466         renderer, "lp", self._fontproperties,
    467         ismath="TeX" if self.get_usetex() else False,
    468         dpi=dpi)
    469     min_ascent = h - min_descent
    470     line_gap = 0

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:59, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
     52 get_text_metrics = _get_text_metrics_function(renderer)
     53 # call the function to compute the metrics and return
     54 #
     55 # We pass a copy of the fontprop because FontProperties is both mutable and
     56 # has a `__hash__` that depends on that mutable state.  This is not ideal
     57 # as it means the hash of an object is not stable over time which leads to
     58 # very confusing behavior when used as keys in dictionaries or hashes.
---> 59 return get_text_metrics(text, fontprop.copy(), ismath, dpi)

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:125, in _get_text_metrics_function.<locals>._text_metrics(text, fontprop, ismath, dpi)
    120     raise RuntimeError(
    121         "Trying to get text metrics for a renderer that no longer exists.  "
    122         "This should never happen and is evidence of a bug elsewhere."
    123         )
    124 # do the actual method call we need and return the result
--> 125 return local_renderer.get_text_width_height_descent(text, fontprop, ismath)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:256, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
    254 _api.check_in_list(["TeX", True, False], ismath=ismath)
    255 if ismath == "TeX":
--> 256     return super().get_text_width_height_descent(s, prop, ismath)
    258 if ismath:
    259     parse = self.mathtext_parser.parse(s, self.dpi, prop)

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:589, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
    585 fontsize = prop.get_size_in_points()
    587 if ismath == 'TeX':
    588     # todo: handle properties
--> 589     return self.get_texmanager().get_text_width_height_descent(
    590         s, fontsize, renderer=self)
    592 dpi = self.points_to_pixels(72)
    593 if ismath:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:369, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
    367 if tex.strip() == '':
    368     return 0, 0, 0
--> 369 dvipath = cls.make_dvi(tex, fontsize)
    370 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
    371 with dviread.Dvi(dvipath, 72 * dpi_fraction) as dvi:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:301, in TexManager.make_dvi(cls, tex, fontsize)
    298 with TemporaryDirectory(dir=dvipath.parent) as tmpdir:
    299     Path(tmpdir, "file.tex").write_text(
    300         cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 301     cls._run_checked_subprocess(
    302         ["latex", "-interaction=nonstopmode", "-halt-on-error",
    303          "-no-shell-escape", "file.tex"], tex, cwd=tmpdir)
    304     Path(tmpdir, "file.dvi").replace(dvipath)
    305     # Also move the tex source to the main cache directory, but
    306     # only for backcompat.

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:274, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    262     raise RuntimeError(
    263         '{prog} was not able to process the following string:\n'
    264         '{tex!r}\n\n'
   (...)    271             exc=exc.output.decode('utf-8', 'backslashreplace'))
    272         ) from None
    273 except (FileNotFoundError, OSError) as exc:
--> 274     raise RuntimeError(
    275         f'Failed to process string with tex because {command[0]} '
    276         'could not be found') from exc
    277 _log.debug(report)
    278 return report

RuntimeError: Failed to process string with tex because latex could not be found
<Figure size 300x300 with 1 Axes>

According to the output above, the sphere problem shows a high fitness-distance correlation between the objective values and distance to the nearest local minimum. This means that the search space is not deceptive, in that a search in the solution space will likely arrive at the global minimum since solutions become fitter and fitter as the search gets closer to the global minimum.

corr, plot = fdc(deceptive_sample)
corr
WARNING:root:No D file is present, thus, computing the D file... Computing an entire D file can be time consuming. Instead, you can call the function with the keyword argument `compute_D_file` set to `False` to speed up computation, as only the required distances will be calculated.
INFO:root:D file has been loaded to the current sample and is saved to ./Deceptive_D.csv
{'fdc_f0': np.float64(-0.7070987098709871)}
Error in callback <function _draw_all_if_interactive at 0x78cde0056e50> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:258, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    257 try:
--> 258     report = subprocess.check_output(
    259         command, cwd=cwd if cwd is not None else cls._cache_dir,
    260         stderr=subprocess.STDOUT)
    261 except subprocess.CalledProcessError as exc:

File /usr/local/lib/python3.14/subprocess.py:473, in check_output(timeout, *popenargs, **kwargs)
    471     kwargs['input'] = empty
--> 473 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    474            **kwargs).stdout

File /usr/local/lib/python3.14/subprocess.py:555, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    553     kwargs['stderr'] = PIPE
--> 555 with Popen(*popenargs, **kwargs) as process:
    556     try:

File /usr/local/lib/python3.14/subprocess.py:1039, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1036             self.stderr = io.TextIOWrapper(self.stderr,
   1037                     encoding=encoding, errors=errors)
-> 1039     self._execute_child(args, executable, preexec_fn, close_fds,
   1040                         pass_fds, cwd, env,
   1041                         startupinfo, creationflags, shell,
   1042                         p2cread, p2cwrite,
   1043                         c2pread, c2pwrite,
   1044                         errread, errwrite,
   1045                         restore_signals,
   1046                         gid, gids, uid, umask,
   1047                         start_new_session, process_group)
   1048 except:
   1049     # Cleanup if the child failed starting.

File /usr/local/lib/python3.14/subprocess.py:1990, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1989 if err_filename is not None:
-> 1990     raise child_exception_type(errno_num, err_msg, err_filename)
   1991 else:

FileNotFoundError: [Errno 2] No such file or directory: 'latex'

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/pyplot.py:300, in _draw_all_if_interactive()
    298 def _draw_all_if_interactive() -> None:
    299     if matplotlib.is_interactive():
--> 300         draw_all()

File /usr/local/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:133, in Gcf.draw_all(cls, force)
    131 for manager in cls.get_all_fig_managers():
    132     if force or manager.canvas.figure.stale:
--> 133         manager.canvas.draw_idle()

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:1989, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
   1987 if not self._is_idle_drawing:
   1988     with self._idle_draw_cntx():
-> 1989         self.draw(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:438, in FigureCanvasAgg.draw(self)
    435 # Acquire a lock on the shared font cache.
    436 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    437       else nullcontext()):
--> 438     self.figure.draw(self.renderer)
    439     # A GUI class may be need to update a window using this draw, so
    440     # don't forget to call the superclass.
    441     super().draw()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /usr/local/lib/python3.14/site-packages/matplotlib/figure.py:3276, in Figure.draw(self, renderer)
   3274 if self.axes and self.get_layout_engine() is not None:
   3275     try:
-> 3276         self.get_layout_engine().execute(self)
   3277     except ValueError:
   3278         pass

File /usr/local/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
    275 w_pad = self._params['w_pad'] / width
    276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
    279                              wspace=self._params['wspace'],
    280                              hspace=self._params['hspace'],
    281                              rect=self._params['rect'],
    282                              compress=self._compress)

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
    106     return
    108 for _ in range(2):
    109     # do the algorithm twice.  This has to be done because decorations
    110     # change size after the first re-position (i.e. x/yticklabels get
   (...)    114     # make margins for all the Axes and subfigures in the
    115     # figure.  Add margins for colorbars...
--> 116     make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
    117                         w_pad=w_pad, hspace=hspace, wspace=wspace)
    118     make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
    119                           w_pad=w_pad)
    121     # if a layout is such that a columns (or rows) margin has no
    122     # constraints, we need to make all such instances in the grid
    123     # match in margin size.

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:389, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
    385     return
    387 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
    388                                  hspace=hspace, wspace=wspace)
--> 389 pos, bbox = get_pos_and_bbox(ax, renderer)
    390 # the margin is the distance between the bounding box of the Axes
    391 # and its position (plus the padding from above)
    392 margin['left'] += pos.x0 - bbox.x0

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:637, in get_pos_and_bbox(ax, renderer)
    635 # pos is in panel co-ords, but we need in figure for the layout
    636 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 637 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
    638 if tightbbox is None:
    639     bbox = pos

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axes/_base.py:4758, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
   4756 if self.axison and axis.get_visible():
   4757     if for_layout_only:
-> 4758         ba = martist._get_tightbbox_for_layout_only(axis, renderer)
   4759     else:
   4760         ba = axis.get_tightbbox(renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1427, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1424     renderer = self.get_figure(root=True)._get_renderer()
   1425 ticks_to_draw = self._update_ticks()
-> 1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels
   1430 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2615, in XAxis._update_label_position(self, renderer)
   2611     return
   2613 # get bounding boxes for this axis and any siblings
   2614 # that have been set by `fig.align_xlabels()`
-> 2615 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
   2616 x, y = self.label.get_position()
   2618 if self.label_position == 'bottom':
   2619     # Union with extents of the bottom spine if present, of the axes otherwise.

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2403, in Axis._get_tick_boxes_siblings(self, renderer)
   2401 axis = ax._axis_map[name]
   2402 ticks_to_draw = axis._update_ticks()
-> 2403 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
   2404 bboxes.extend(tlb)
   2405 bboxes2.extend(tlb2)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1404, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
   1402 def _get_ticklabel_bboxes(self, ticks, renderer):
   1403     """Return lists of bboxes for ticks' label1's and label2's."""
-> 1404     return ([tick.label1.get_window_extent(renderer)
   1405              for tick in ticks
   1406              if tick.label1.get_visible() and tick.label1.get_in_layout()],
   1407             [tick.label2.get_window_extent(renderer)
   1408              for tick in ticks
   1409              if tick.label2.get_visible() and tick.label2.get_in_layout()])

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:1076, in Text.get_window_extent(self, renderer, dpi)
   1071     raise RuntimeError(
   1072         "Cannot get window extent of text w/o renderer. You likely "
   1073         "want to call 'figure.draw_without_rendering()' first.")
   1075 with cbook._setattr_cm(fig, dpi=dpi):
-> 1076     bbox, _, _ = self._get_layout(self._renderer)
   1077     x, y = self.get_unitless_position()
   1078     x, y = self.get_transform().transform((x, y))

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:465, in Text._get_layout(self, renderer)
    462             break
    463 if None in (min_ascent, min_descent):
    464     # Fallback to font measurement.
--> 465     _, h, min_descent = _get_text_metrics_with_cache(
    466         renderer, "lp", self._fontproperties,
    467         ismath="TeX" if self.get_usetex() else False,
    468         dpi=dpi)
    469     min_ascent = h - min_descent
    470     line_gap = 0

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:59, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
     52 get_text_metrics = _get_text_metrics_function(renderer)
     53 # call the function to compute the metrics and return
     54 #
     55 # We pass a copy of the fontprop because FontProperties is both mutable and
     56 # has a `__hash__` that depends on that mutable state.  This is not ideal
     57 # as it means the hash of an object is not stable over time which leads to
     58 # very confusing behavior when used as keys in dictionaries or hashes.
---> 59 return get_text_metrics(text, fontprop.copy(), ismath, dpi)

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:125, in _get_text_metrics_function.<locals>._text_metrics(text, fontprop, ismath, dpi)
    120     raise RuntimeError(
    121         "Trying to get text metrics for a renderer that no longer exists.  "
    122         "This should never happen and is evidence of a bug elsewhere."
    123         )
    124 # do the actual method call we need and return the result
--> 125 return local_renderer.get_text_width_height_descent(text, fontprop, ismath)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:256, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
    254 _api.check_in_list(["TeX", True, False], ismath=ismath)
    255 if ismath == "TeX":
--> 256     return super().get_text_width_height_descent(s, prop, ismath)
    258 if ismath:
    259     parse = self.mathtext_parser.parse(s, self.dpi, prop)

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:589, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
    585 fontsize = prop.get_size_in_points()
    587 if ismath == 'TeX':
    588     # todo: handle properties
--> 589     return self.get_texmanager().get_text_width_height_descent(
    590         s, fontsize, renderer=self)
    592 dpi = self.points_to_pixels(72)
    593 if ismath:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:369, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
    367 if tex.strip() == '':
    368     return 0, 0, 0
--> 369 dvipath = cls.make_dvi(tex, fontsize)
    370 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
    371 with dviread.Dvi(dvipath, 72 * dpi_fraction) as dvi:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:301, in TexManager.make_dvi(cls, tex, fontsize)
    298 with TemporaryDirectory(dir=dvipath.parent) as tmpdir:
    299     Path(tmpdir, "file.tex").write_text(
    300         cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 301     cls._run_checked_subprocess(
    302         ["latex", "-interaction=nonstopmode", "-halt-on-error",
    303          "-no-shell-escape", "file.tex"], tex, cwd=tmpdir)
    304     Path(tmpdir, "file.dvi").replace(dvipath)
    305     # Also move the tex source to the main cache directory, but
    306     # only for backcompat.

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:274, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    262     raise RuntimeError(
    263         '{prog} was not able to process the following string:\n'
    264         '{tex!r}\n\n'
   (...)    271             exc=exc.output.decode('utf-8', 'backslashreplace'))
    272         ) from None
    273 except (FileNotFoundError, OSError) as exc:
--> 274     raise RuntimeError(
    275         f'Failed to process string with tex because {command[0]} '
    276         'could not be found') from exc
    277 _log.debug(report)
    278 return report

RuntimeError: Failed to process string with tex because latex could not be found
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:258, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    257 try:
--> 258     report = subprocess.check_output(
    259         command, cwd=cwd if cwd is not None else cls._cache_dir,
    260         stderr=subprocess.STDOUT)
    261 except subprocess.CalledProcessError as exc:

File /usr/local/lib/python3.14/subprocess.py:473, in check_output(timeout, *popenargs, **kwargs)
    471     kwargs['input'] = empty
--> 473 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    474            **kwargs).stdout

File /usr/local/lib/python3.14/subprocess.py:555, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    553     kwargs['stderr'] = PIPE
--> 555 with Popen(*popenargs, **kwargs) as process:
    556     try:

File /usr/local/lib/python3.14/subprocess.py:1039, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1036             self.stderr = io.TextIOWrapper(self.stderr,
   1037                     encoding=encoding, errors=errors)
-> 1039     self._execute_child(args, executable, preexec_fn, close_fds,
   1040                         pass_fds, cwd, env,
   1041                         startupinfo, creationflags, shell,
   1042                         p2cread, p2cwrite,
   1043                         c2pread, c2pwrite,
   1044                         errread, errwrite,
   1045                         restore_signals,
   1046                         gid, gids, uid, umask,
   1047                         start_new_session, process_group)
   1048 except:
   1049     # Cleanup if the child failed starting.

File /usr/local/lib/python3.14/subprocess.py:1990, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1989 if err_filename is not None:
-> 1990     raise child_exception_type(errno_num, err_msg, err_filename)
   1991 else:

FileNotFoundError: [Errno 2] No such file or directory: 'latex'

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:2257, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2254     # we do this instead of `self.figure.draw_without_rendering`
   2255     # so that we can inject the orientation
   2256     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2257         self.figure.draw(renderer)
   2258 else:
   2259     renderer = None

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /usr/local/lib/python3.14/site-packages/matplotlib/figure.py:3276, in Figure.draw(self, renderer)
   3274 if self.axes and self.get_layout_engine() is not None:
   3275     try:
-> 3276         self.get_layout_engine().execute(self)
   3277     except ValueError:
   3278         pass

File /usr/local/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
    275 w_pad = self._params['w_pad'] / width
    276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
    279                              wspace=self._params['wspace'],
    280                              hspace=self._params['hspace'],
    281                              rect=self._params['rect'],
    282                              compress=self._compress)

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
    106     return
    108 for _ in range(2):
    109     # do the algorithm twice.  This has to be done because decorations
    110     # change size after the first re-position (i.e. x/yticklabels get
   (...)    114     # make margins for all the Axes and subfigures in the
    115     # figure.  Add margins for colorbars...
--> 116     make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
    117                         w_pad=w_pad, hspace=hspace, wspace=wspace)
    118     make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
    119                           w_pad=w_pad)
    121     # if a layout is such that a columns (or rows) margin has no
    122     # constraints, we need to make all such instances in the grid
    123     # match in margin size.

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:389, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
    385     return
    387 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
    388                                  hspace=hspace, wspace=wspace)
--> 389 pos, bbox = get_pos_and_bbox(ax, renderer)
    390 # the margin is the distance between the bounding box of the Axes
    391 # and its position (plus the padding from above)
    392 margin['left'] += pos.x0 - bbox.x0

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:637, in get_pos_and_bbox(ax, renderer)
    635 # pos is in panel co-ords, but we need in figure for the layout
    636 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 637 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
    638 if tightbbox is None:
    639     bbox = pos

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axes/_base.py:4758, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
   4756 if self.axison and axis.get_visible():
   4757     if for_layout_only:
-> 4758         ba = martist._get_tightbbox_for_layout_only(axis, renderer)
   4759     else:
   4760         ba = axis.get_tightbbox(renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1427, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1424     renderer = self.get_figure(root=True)._get_renderer()
   1425 ticks_to_draw = self._update_ticks()
-> 1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels
   1430 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2615, in XAxis._update_label_position(self, renderer)
   2611     return
   2613 # get bounding boxes for this axis and any siblings
   2614 # that have been set by `fig.align_xlabels()`
-> 2615 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
   2616 x, y = self.label.get_position()
   2618 if self.label_position == 'bottom':
   2619     # Union with extents of the bottom spine if present, of the axes otherwise.

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2403, in Axis._get_tick_boxes_siblings(self, renderer)
   2401 axis = ax._axis_map[name]
   2402 ticks_to_draw = axis._update_ticks()
-> 2403 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
   2404 bboxes.extend(tlb)
   2405 bboxes2.extend(tlb2)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1404, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
   1402 def _get_ticklabel_bboxes(self, ticks, renderer):
   1403     """Return lists of bboxes for ticks' label1's and label2's."""
-> 1404     return ([tick.label1.get_window_extent(renderer)
   1405              for tick in ticks
   1406              if tick.label1.get_visible() and tick.label1.get_in_layout()],
   1407             [tick.label2.get_window_extent(renderer)
   1408              for tick in ticks
   1409              if tick.label2.get_visible() and tick.label2.get_in_layout()])

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:1076, in Text.get_window_extent(self, renderer, dpi)
   1071     raise RuntimeError(
   1072         "Cannot get window extent of text w/o renderer. You likely "
   1073         "want to call 'figure.draw_without_rendering()' first.")
   1075 with cbook._setattr_cm(fig, dpi=dpi):
-> 1076     bbox, _, _ = self._get_layout(self._renderer)
   1077     x, y = self.get_unitless_position()
   1078     x, y = self.get_transform().transform((x, y))

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:465, in Text._get_layout(self, renderer)
    462             break
    463 if None in (min_ascent, min_descent):
    464     # Fallback to font measurement.
--> 465     _, h, min_descent = _get_text_metrics_with_cache(
    466         renderer, "lp", self._fontproperties,
    467         ismath="TeX" if self.get_usetex() else False,
    468         dpi=dpi)
    469     min_ascent = h - min_descent
    470     line_gap = 0

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:59, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
     52 get_text_metrics = _get_text_metrics_function(renderer)
     53 # call the function to compute the metrics and return
     54 #
     55 # We pass a copy of the fontprop because FontProperties is both mutable and
     56 # has a `__hash__` that depends on that mutable state.  This is not ideal
     57 # as it means the hash of an object is not stable over time which leads to
     58 # very confusing behavior when used as keys in dictionaries or hashes.
---> 59 return get_text_metrics(text, fontprop.copy(), ismath, dpi)

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:125, in _get_text_metrics_function.<locals>._text_metrics(text, fontprop, ismath, dpi)
    120     raise RuntimeError(
    121         "Trying to get text metrics for a renderer that no longer exists.  "
    122         "This should never happen and is evidence of a bug elsewhere."
    123         )
    124 # do the actual method call we need and return the result
--> 125 return local_renderer.get_text_width_height_descent(text, fontprop, ismath)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:256, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
    254 _api.check_in_list(["TeX", True, False], ismath=ismath)
    255 if ismath == "TeX":
--> 256     return super().get_text_width_height_descent(s, prop, ismath)
    258 if ismath:
    259     parse = self.mathtext_parser.parse(s, self.dpi, prop)

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:589, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
    585 fontsize = prop.get_size_in_points()
    587 if ismath == 'TeX':
    588     # todo: handle properties
--> 589     return self.get_texmanager().get_text_width_height_descent(
    590         s, fontsize, renderer=self)
    592 dpi = self.points_to_pixels(72)
    593 if ismath:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:369, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
    367 if tex.strip() == '':
    368     return 0, 0, 0
--> 369 dvipath = cls.make_dvi(tex, fontsize)
    370 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
    371 with dviread.Dvi(dvipath, 72 * dpi_fraction) as dvi:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:301, in TexManager.make_dvi(cls, tex, fontsize)
    298 with TemporaryDirectory(dir=dvipath.parent) as tmpdir:
    299     Path(tmpdir, "file.tex").write_text(
    300         cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 301     cls._run_checked_subprocess(
    302         ["latex", "-interaction=nonstopmode", "-halt-on-error",
    303          "-no-shell-escape", "file.tex"], tex, cwd=tmpdir)
    304     Path(tmpdir, "file.dvi").replace(dvipath)
    305     # Also move the tex source to the main cache directory, but
    306     # only for backcompat.

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:274, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    262     raise RuntimeError(
    263         '{prog} was not able to process the following string:\n'
    264         '{tex!r}\n\n'
   (...)    271             exc=exc.output.decode('utf-8', 'backslashreplace'))
    272         ) from None
    273 except (FileNotFoundError, OSError) as exc:
--> 274     raise RuntimeError(
    275         f'Failed to process string with tex because {command[0]} '
    276         'could not be found') from exc
    277 _log.debug(report)
    278 return report

RuntimeError: Failed to process string with tex because latex could not be found
<Figure size 300x300 with 1 Axes>

Conversely, the output above shows a negative correlation between distance to the nearest local minimum and objective values for the deceptive problem. Therefore, as one nears the global minimum, solutions tend to deteriorate in fitness, signalling the presence of multiple local minima. In this case, pyxla.fdc() indicates that the problem is indeed deceptive.

Rank distance correlation (RDC)

pyxla.rdc() corrobotates pyxla.fdc() since it operates on the objective ranks rather than the values.

from pyxla import rdc

corr, plot = rdc(sphere_sample)
corr
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[6], line 3
      1 from pyxla import rdc
      2 
----> 3 corr, plot = rdc(sphere_sample)
      4 corr

File /builds/aliefooghe/pyxla/src/pyxla/__init__.py:1011, in rdc(sample, compute_D_file)
   1008         d_nearest_best = min(d_nearest_best, d)
   1009     RDC.loc[t_idx, "distance"] = d_nearest_best
-> 1011 sp_corr, _ = scipy.stats.spearmanr(RDC[col], RDC["distance"])
   1012 corrs.update({f"rdc_{col}": sp_corr})
   1014 ax = axs[i] if numR > 1 else axs

File /usr/local/lib/python3.14/site-packages/scipy/stats/_stats_py.py:5446, in spearmanr(a, b, axis, nan_policy, alternative)
   5442     if constant_axis:
   5443         with np.errstate(invalid='ignore'):
   5444             rs = np.corrcoef(a_ranked, rowvar=axisout)
   5445     else:
-> 5446         rs = np.corrcoef(a_ranked, rowvar=axisout)
   5447 
   5448     dof = n_obs - 2  # degrees of freedom
   5449 

File /usr/local/lib/python3.14/site-packages/numpy/lib/_function_base_impl.py:3028, in corrcoef(x, y, rowvar, dtype)
   2910 @array_function_dispatch(_corrcoef_dispatcher)
   2911 def corrcoef(x, y=None, rowvar=True, *,
   2912              dtype=None):
   2913     """
   2914     Return Pearson product-moment correlation coefficients.
   2915 
   (...)   3026 
   3027     """
-> 3028     c = cov(x, y, rowvar, dtype=dtype)
   3029     try:
   3030         d = diag(c)

File /usr/local/lib/python3.14/site-packages/numpy/lib/_function_base_impl.py:2877, in cov(m, y, rowvar, bias, ddof, fweights, aweights, dtype)
   2874     else:
   2875         w *= aweights
-> 2877 avg, w_sum = average(X, axis=1, weights=w, returned=True)
   2878 w_sum = w_sum[0]
   2880 # Determine the normalization

File /usr/local/lib/python3.14/site-packages/numpy/lib/_function_base_impl.py:607, in average(a, axis, weights, returned, keepdims)
    603     avg = avg_as_array = np.multiply(a, wgt,
    604                       dtype=result_dtype).sum(axis, **keepdims_kw) / scl
    606 if returned:
--> 607     if scl.shape != avg_as_array.shape:
    608         scl = np.broadcast_to(scl, avg_as_array.shape, subok=True).copy()
    609     return avg, scl

AttributeError: 'float' object has no attribute 'shape'
Error in callback <function _draw_all_if_interactive at 0x78cde0056e50> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:258, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    257 try:
--> 258     report = subprocess.check_output(
    259         command, cwd=cwd if cwd is not None else cls._cache_dir,
    260         stderr=subprocess.STDOUT)
    261 except subprocess.CalledProcessError as exc:

File /usr/local/lib/python3.14/subprocess.py:473, in check_output(timeout, *popenargs, **kwargs)
    471     kwargs['input'] = empty
--> 473 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    474            **kwargs).stdout

File /usr/local/lib/python3.14/subprocess.py:555, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    553     kwargs['stderr'] = PIPE
--> 555 with Popen(*popenargs, **kwargs) as process:
    556     try:

File /usr/local/lib/python3.14/subprocess.py:1039, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1036             self.stderr = io.TextIOWrapper(self.stderr,
   1037                     encoding=encoding, errors=errors)
-> 1039     self._execute_child(args, executable, preexec_fn, close_fds,
   1040                         pass_fds, cwd, env,
   1041                         startupinfo, creationflags, shell,
   1042                         p2cread, p2cwrite,
   1043                         c2pread, c2pwrite,
   1044                         errread, errwrite,
   1045                         restore_signals,
   1046                         gid, gids, uid, umask,
   1047                         start_new_session, process_group)
   1048 except:
   1049     # Cleanup if the child failed starting.

File /usr/local/lib/python3.14/subprocess.py:1990, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1989 if err_filename is not None:
-> 1990     raise child_exception_type(errno_num, err_msg, err_filename)
   1991 else:

FileNotFoundError: [Errno 2] No such file or directory: 'latex'

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/pyplot.py:300, in _draw_all_if_interactive()
    298 def _draw_all_if_interactive() -> None:
    299     if matplotlib.is_interactive():
--> 300         draw_all()

File /usr/local/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:133, in Gcf.draw_all(cls, force)
    131 for manager in cls.get_all_fig_managers():
    132     if force or manager.canvas.figure.stale:
--> 133         manager.canvas.draw_idle()

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:1989, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
   1987 if not self._is_idle_drawing:
   1988     with self._idle_draw_cntx():
-> 1989         self.draw(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:438, in FigureCanvasAgg.draw(self)
    435 # Acquire a lock on the shared font cache.
    436 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    437       else nullcontext()):
--> 438     self.figure.draw(self.renderer)
    439     # A GUI class may be need to update a window using this draw, so
    440     # don't forget to call the superclass.
    441     super().draw()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /usr/local/lib/python3.14/site-packages/matplotlib/figure.py:3276, in Figure.draw(self, renderer)
   3274 if self.axes and self.get_layout_engine() is not None:
   3275     try:
-> 3276         self.get_layout_engine().execute(self)
   3277     except ValueError:
   3278         pass

File /usr/local/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
    275 w_pad = self._params['w_pad'] / width
    276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
    279                              wspace=self._params['wspace'],
    280                              hspace=self._params['hspace'],
    281                              rect=self._params['rect'],
    282                              compress=self._compress)

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
    106     return
    108 for _ in range(2):
    109     # do the algorithm twice.  This has to be done because decorations
    110     # change size after the first re-position (i.e. x/yticklabels get
   (...)    114     # make margins for all the Axes and subfigures in the
    115     # figure.  Add margins for colorbars...
--> 116     make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
    117                         w_pad=w_pad, hspace=hspace, wspace=wspace)
    118     make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
    119                           w_pad=w_pad)
    121     # if a layout is such that a columns (or rows) margin has no
    122     # constraints, we need to make all such instances in the grid
    123     # match in margin size.

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:389, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
    385     return
    387 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
    388                                  hspace=hspace, wspace=wspace)
--> 389 pos, bbox = get_pos_and_bbox(ax, renderer)
    390 # the margin is the distance between the bounding box of the Axes
    391 # and its position (plus the padding from above)
    392 margin['left'] += pos.x0 - bbox.x0

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:637, in get_pos_and_bbox(ax, renderer)
    635 # pos is in panel co-ords, but we need in figure for the layout
    636 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 637 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
    638 if tightbbox is None:
    639     bbox = pos

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axes/_base.py:4758, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
   4756 if self.axison and axis.get_visible():
   4757     if for_layout_only:
-> 4758         ba = martist._get_tightbbox_for_layout_only(axis, renderer)
   4759     else:
   4760         ba = axis.get_tightbbox(renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1427, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1424     renderer = self.get_figure(root=True)._get_renderer()
   1425 ticks_to_draw = self._update_ticks()
-> 1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels
   1430 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2615, in XAxis._update_label_position(self, renderer)
   2611     return
   2613 # get bounding boxes for this axis and any siblings
   2614 # that have been set by `fig.align_xlabels()`
-> 2615 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
   2616 x, y = self.label.get_position()
   2618 if self.label_position == 'bottom':
   2619     # Union with extents of the bottom spine if present, of the axes otherwise.

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2403, in Axis._get_tick_boxes_siblings(self, renderer)
   2401 axis = ax._axis_map[name]
   2402 ticks_to_draw = axis._update_ticks()
-> 2403 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
   2404 bboxes.extend(tlb)
   2405 bboxes2.extend(tlb2)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1404, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
   1402 def _get_ticklabel_bboxes(self, ticks, renderer):
   1403     """Return lists of bboxes for ticks' label1's and label2's."""
-> 1404     return ([tick.label1.get_window_extent(renderer)
   1405              for tick in ticks
   1406              if tick.label1.get_visible() and tick.label1.get_in_layout()],
   1407             [tick.label2.get_window_extent(renderer)
   1408              for tick in ticks
   1409              if tick.label2.get_visible() and tick.label2.get_in_layout()])

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:1076, in Text.get_window_extent(self, renderer, dpi)
   1071     raise RuntimeError(
   1072         "Cannot get window extent of text w/o renderer. You likely "
   1073         "want to call 'figure.draw_without_rendering()' first.")
   1075 with cbook._setattr_cm(fig, dpi=dpi):
-> 1076     bbox, _, _ = self._get_layout(self._renderer)
   1077     x, y = self.get_unitless_position()
   1078     x, y = self.get_transform().transform((x, y))

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:465, in Text._get_layout(self, renderer)
    462             break
    463 if None in (min_ascent, min_descent):
    464     # Fallback to font measurement.
--> 465     _, h, min_descent = _get_text_metrics_with_cache(
    466         renderer, "lp", self._fontproperties,
    467         ismath="TeX" if self.get_usetex() else False,
    468         dpi=dpi)
    469     min_ascent = h - min_descent
    470     line_gap = 0

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:59, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
     52 get_text_metrics = _get_text_metrics_function(renderer)
     53 # call the function to compute the metrics and return
     54 #
     55 # We pass a copy of the fontprop because FontProperties is both mutable and
     56 # has a `__hash__` that depends on that mutable state.  This is not ideal
     57 # as it means the hash of an object is not stable over time which leads to
     58 # very confusing behavior when used as keys in dictionaries or hashes.
---> 59 return get_text_metrics(text, fontprop.copy(), ismath, dpi)

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:125, in _get_text_metrics_function.<locals>._text_metrics(text, fontprop, ismath, dpi)
    120     raise RuntimeError(
    121         "Trying to get text metrics for a renderer that no longer exists.  "
    122         "This should never happen and is evidence of a bug elsewhere."
    123         )
    124 # do the actual method call we need and return the result
--> 125 return local_renderer.get_text_width_height_descent(text, fontprop, ismath)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:256, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
    254 _api.check_in_list(["TeX", True, False], ismath=ismath)
    255 if ismath == "TeX":
--> 256     return super().get_text_width_height_descent(s, prop, ismath)
    258 if ismath:
    259     parse = self.mathtext_parser.parse(s, self.dpi, prop)

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:589, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
    585 fontsize = prop.get_size_in_points()
    587 if ismath == 'TeX':
    588     # todo: handle properties
--> 589     return self.get_texmanager().get_text_width_height_descent(
    590         s, fontsize, renderer=self)
    592 dpi = self.points_to_pixels(72)
    593 if ismath:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:369, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
    367 if tex.strip() == '':
    368     return 0, 0, 0
--> 369 dvipath = cls.make_dvi(tex, fontsize)
    370 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
    371 with dviread.Dvi(dvipath, 72 * dpi_fraction) as dvi:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:301, in TexManager.make_dvi(cls, tex, fontsize)
    298 with TemporaryDirectory(dir=dvipath.parent) as tmpdir:
    299     Path(tmpdir, "file.tex").write_text(
    300         cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 301     cls._run_checked_subprocess(
    302         ["latex", "-interaction=nonstopmode", "-halt-on-error",
    303          "-no-shell-escape", "file.tex"], tex, cwd=tmpdir)
    304     Path(tmpdir, "file.dvi").replace(dvipath)
    305     # Also move the tex source to the main cache directory, but
    306     # only for backcompat.

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:274, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    262     raise RuntimeError(
    263         '{prog} was not able to process the following string:\n'
    264         '{tex!r}\n\n'
   (...)    271             exc=exc.output.decode('utf-8', 'backslashreplace'))
    272         ) from None
    273 except (FileNotFoundError, OSError) as exc:
--> 274     raise RuntimeError(
    275         f'Failed to process string with tex because {command[0]} '
    276         'could not be found') from exc
    277 _log.debug(report)
    278 return report

RuntimeError: Failed to process string with tex because latex could not be found
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:258, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    257 try:
--> 258     report = subprocess.check_output(
    259         command, cwd=cwd if cwd is not None else cls._cache_dir,
    260         stderr=subprocess.STDOUT)
    261 except subprocess.CalledProcessError as exc:

File /usr/local/lib/python3.14/subprocess.py:473, in check_output(timeout, *popenargs, **kwargs)
    471     kwargs['input'] = empty
--> 473 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    474            **kwargs).stdout

File /usr/local/lib/python3.14/subprocess.py:555, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    553     kwargs['stderr'] = PIPE
--> 555 with Popen(*popenargs, **kwargs) as process:
    556     try:

File /usr/local/lib/python3.14/subprocess.py:1039, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1036             self.stderr = io.TextIOWrapper(self.stderr,
   1037                     encoding=encoding, errors=errors)
-> 1039     self._execute_child(args, executable, preexec_fn, close_fds,
   1040                         pass_fds, cwd, env,
   1041                         startupinfo, creationflags, shell,
   1042                         p2cread, p2cwrite,
   1043                         c2pread, c2pwrite,
   1044                         errread, errwrite,
   1045                         restore_signals,
   1046                         gid, gids, uid, umask,
   1047                         start_new_session, process_group)
   1048 except:
   1049     # Cleanup if the child failed starting.

File /usr/local/lib/python3.14/subprocess.py:1990, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1989 if err_filename is not None:
-> 1990     raise child_exception_type(errno_num, err_msg, err_filename)
   1991 else:

FileNotFoundError: [Errno 2] No such file or directory: 'latex'

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:2257, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2254     # we do this instead of `self.figure.draw_without_rendering`
   2255     # so that we can inject the orientation
   2256     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2257         self.figure.draw(renderer)
   2258 else:
   2259     renderer = None

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /usr/local/lib/python3.14/site-packages/matplotlib/figure.py:3276, in Figure.draw(self, renderer)
   3274 if self.axes and self.get_layout_engine() is not None:
   3275     try:
-> 3276         self.get_layout_engine().execute(self)
   3277     except ValueError:
   3278         pass

File /usr/local/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
    275 w_pad = self._params['w_pad'] / width
    276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
    279                              wspace=self._params['wspace'],
    280                              hspace=self._params['hspace'],
    281                              rect=self._params['rect'],
    282                              compress=self._compress)

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
    106     return
    108 for _ in range(2):
    109     # do the algorithm twice.  This has to be done because decorations
    110     # change size after the first re-position (i.e. x/yticklabels get
   (...)    114     # make margins for all the Axes and subfigures in the
    115     # figure.  Add margins for colorbars...
--> 116     make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
    117                         w_pad=w_pad, hspace=hspace, wspace=wspace)
    118     make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
    119                           w_pad=w_pad)
    121     # if a layout is such that a columns (or rows) margin has no
    122     # constraints, we need to make all such instances in the grid
    123     # match in margin size.

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:389, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
    385     return
    387 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
    388                                  hspace=hspace, wspace=wspace)
--> 389 pos, bbox = get_pos_and_bbox(ax, renderer)
    390 # the margin is the distance between the bounding box of the Axes
    391 # and its position (plus the padding from above)
    392 margin['left'] += pos.x0 - bbox.x0

File /usr/local/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:637, in get_pos_and_bbox(ax, renderer)
    635 # pos is in panel co-ords, but we need in figure for the layout
    636 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 637 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
    638 if tightbbox is None:
    639     bbox = pos

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axes/_base.py:4758, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
   4756 if self.axison and axis.get_visible():
   4757     if for_layout_only:
-> 4758         ba = martist._get_tightbbox_for_layout_only(axis, renderer)
   4759     else:
   4760         ba = axis.get_tightbbox(renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/artist.py:1485, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
   1479 """
   1480 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
   1481 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
   1482 when encountering third-party subclasses that do not support it.
   1483 """
   1484 try:
-> 1485     return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
   1486 except TypeError:
   1487     return obj.get_tightbbox(*args, **kwargs)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1427, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1424     renderer = self.get_figure(root=True)._get_renderer()
   1425 ticks_to_draw = self._update_ticks()
-> 1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels
   1430 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2615, in XAxis._update_label_position(self, renderer)
   2611     return
   2613 # get bounding boxes for this axis and any siblings
   2614 # that have been set by `fig.align_xlabels()`
-> 2615 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
   2616 x, y = self.label.get_position()
   2618 if self.label_position == 'bottom':
   2619     # Union with extents of the bottom spine if present, of the axes otherwise.

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:2403, in Axis._get_tick_boxes_siblings(self, renderer)
   2401 axis = ax._axis_map[name]
   2402 ticks_to_draw = axis._update_ticks()
-> 2403 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
   2404 bboxes.extend(tlb)
   2405 bboxes2.extend(tlb2)

File /usr/local/lib/python3.14/site-packages/matplotlib/axis.py:1404, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
   1402 def _get_ticklabel_bboxes(self, ticks, renderer):
   1403     """Return lists of bboxes for ticks' label1's and label2's."""
-> 1404     return ([tick.label1.get_window_extent(renderer)
   1405              for tick in ticks
   1406              if tick.label1.get_visible() and tick.label1.get_in_layout()],
   1407             [tick.label2.get_window_extent(renderer)
   1408              for tick in ticks
   1409              if tick.label2.get_visible() and tick.label2.get_in_layout()])

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:1076, in Text.get_window_extent(self, renderer, dpi)
   1071     raise RuntimeError(
   1072         "Cannot get window extent of text w/o renderer. You likely "
   1073         "want to call 'figure.draw_without_rendering()' first.")
   1075 with cbook._setattr_cm(fig, dpi=dpi):
-> 1076     bbox, _, _ = self._get_layout(self._renderer)
   1077     x, y = self.get_unitless_position()
   1078     x, y = self.get_transform().transform((x, y))

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:465, in Text._get_layout(self, renderer)
    462             break
    463 if None in (min_ascent, min_descent):
    464     # Fallback to font measurement.
--> 465     _, h, min_descent = _get_text_metrics_with_cache(
    466         renderer, "lp", self._fontproperties,
    467         ismath="TeX" if self.get_usetex() else False,
    468         dpi=dpi)
    469     min_ascent = h - min_descent
    470     line_gap = 0

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:59, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
     52 get_text_metrics = _get_text_metrics_function(renderer)
     53 # call the function to compute the metrics and return
     54 #
     55 # We pass a copy of the fontprop because FontProperties is both mutable and
     56 # has a `__hash__` that depends on that mutable state.  This is not ideal
     57 # as it means the hash of an object is not stable over time which leads to
     58 # very confusing behavior when used as keys in dictionaries or hashes.
---> 59 return get_text_metrics(text, fontprop.copy(), ismath, dpi)

File /usr/local/lib/python3.14/site-packages/matplotlib/text.py:125, in _get_text_metrics_function.<locals>._text_metrics(text, fontprop, ismath, dpi)
    120     raise RuntimeError(
    121         "Trying to get text metrics for a renderer that no longer exists.  "
    122         "This should never happen and is evidence of a bug elsewhere."
    123         )
    124 # do the actual method call we need and return the result
--> 125 return local_renderer.get_text_width_height_descent(text, fontprop, ismath)

File /usr/local/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:256, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
    254 _api.check_in_list(["TeX", True, False], ismath=ismath)
    255 if ismath == "TeX":
--> 256     return super().get_text_width_height_descent(s, prop, ismath)
    258 if ismath:
    259     parse = self.mathtext_parser.parse(s, self.dpi, prop)

File /usr/local/lib/python3.14/site-packages/matplotlib/backend_bases.py:589, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
    585 fontsize = prop.get_size_in_points()
    587 if ismath == 'TeX':
    588     # todo: handle properties
--> 589     return self.get_texmanager().get_text_width_height_descent(
    590         s, fontsize, renderer=self)
    592 dpi = self.points_to_pixels(72)
    593 if ismath:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:369, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
    367 if tex.strip() == '':
    368     return 0, 0, 0
--> 369 dvipath = cls.make_dvi(tex, fontsize)
    370 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
    371 with dviread.Dvi(dvipath, 72 * dpi_fraction) as dvi:

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:301, in TexManager.make_dvi(cls, tex, fontsize)
    298 with TemporaryDirectory(dir=dvipath.parent) as tmpdir:
    299     Path(tmpdir, "file.tex").write_text(
    300         cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 301     cls._run_checked_subprocess(
    302         ["latex", "-interaction=nonstopmode", "-halt-on-error",
    303          "-no-shell-escape", "file.tex"], tex, cwd=tmpdir)
    304     Path(tmpdir, "file.dvi").replace(dvipath)
    305     # Also move the tex source to the main cache directory, but
    306     # only for backcompat.

File /usr/local/lib/python3.14/site-packages/matplotlib/texmanager.py:274, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    262     raise RuntimeError(
    263         '{prog} was not able to process the following string:\n'
    264         '{tex!r}\n\n'
   (...)    271             exc=exc.output.decode('utf-8', 'backslashreplace'))
    272         ) from None
    273 except (FileNotFoundError, OSError) as exc:
--> 274     raise RuntimeError(
    275         f'Failed to process string with tex because {command[0]} '
    276         'could not be found') from exc
    277 _log.debug(report)
    278 return report

RuntimeError: Failed to process string with tex because latex could not be found
<Figure size 900x300 with 3 Axes>
corr, plot = rdc(deceptive_sample)
corr
{'rdc_f0': np.float64(-0.597095709570957)}
../../_images/03f6846e037e78419d714788f04e64819237c06460a4803a0cccb5be029ce731.png

Pairwise distance correlation (PDC)

from pyxla import pdc

corr, plot = pdc(sphere_sample)
corr
{'pdc_F': np.float64(0.4387542029401414),
 'pdc_f0-rank': np.float64(0.34382468358143525)}
../../_images/0f371422877bea6493658037b13fe298e00a7ab714f97aa0f054044d1d80058f.png

pyxla.pdc() shows that for the sphere function, there is low correlation between pairwise distance on the solution space and pairwise distance on the objective space. This shows that the sphere function is not monotonic.

corr, plot = pdc(deceptive_sample)
corr
{'pdc_F': np.float64(0.6247890687938944),
 'pdc_f0-rank': np.float64(0.6685896526930889)}
../../_images/2d4c87ed2bf1d6882abdbf28d8fa1bbbbcc1b75e4148160f1f83659a3e4b99c8.png

Similarly the deceptive function it not monotonic and appears more rugged that the sphere function.

Neighbouring solutions’ objective values (fitness) correlation (NFC)

from pyxla import nfc

corr, plot = nfc(sphere_sample)
corr
{'nfc_all_X_for_f0': np.float64(0.9972665429808286)}
../../_images/23af6f7708b8eac8d38911df37b671e0841d182fee392dd40e70e02cf218731f.png

The constrained sphere problem is found to have high evolvability, evidenced by a high NFC value. This is expected, since the sphere function is one of the simplest continuous optimisation problems.

corr, plot = nfc(deceptive_sample)
corr
{'nfc_all_X_for_f0': np.float64(0.9265182436611007)}
../../_images/1e478159940143182daba244318a6fecef961174c074dc780ae32b83b3c19862.png

The deceptive problem has a high NFC but a lower value that the sphere function as expected, since the sphere problem is an easier problem.

Neighbouring solutions’ ranks correlation (NRC)

pyxla.nrc() corroborates pyxla.nfc() since it is based on the objective ranks.

from pyxla import nrc

corr, plot = nrc(sphere_sample)
corr
{'NRC_for_f0_ranks': '0.9973'}
../../_images/e66afa4cf622c111903a874f92a46aaf85661cbc738d38dbb3dba73c8a30d797.png
corr, plot = nrc(deceptive_sample)
corr
{'NRC_for_f0_ranks': '0.9265'}
../../_images/72e5ff2440502aa19c2499e4597dc621456f1798ba1528ee4342388ad0e82051.png

Dispersion of best solution (disp_best)

pyxla.disp_best() can tell if the problem has a global structure or whether it has many funnels.

from pyxla import disp_best

corr, plot = disp_best(sphere_sample)
corr
corr, plot = disp_best(deceptive_sample)
corr

The sphere problem’s objective has a negative dispersion metric value which indicates the presence of a global structure, as expected. The deceptive problem, on the other hand, has a positive dispersion metric value, indicating the presence of funnels. The scatter plots visually express the dispersion, where for the constrained sphere problem, the spread of pairwise distances grows as the sample size of the best solution is increased. Conversely, for the deceptive problem, the gaps between the clusters of points for lower sample sizes show that indeed the best solutions are much further apart, accounting for the lack of a global structure.