Chapter 8: Recursive Decision Processes#

quantile_function.py#

Hide code cell source
from scipy.stats import rv_discrete
import numpy as np

from numba import njit

"Compute the τ-th quantile of v(X) when X ∼ ϕ and v = sort(v)."
@njit
def quantile(τ, v, ϕ):
    for (i, v_value) in enumerate(v):
        p = sum(ϕ[:i+1])  # sum all ϕ[j] s.t. v[j] ≤ v_value
        if p >= τ:         # exit and return v_value if prob ≥ τ
            return v_value

"For each i, compute the τ-th quantile of v(Y) when Y ∼ P(i, ⋅)"
def R(τ, v, P):
    return np.array([quantile(τ, v, P[i, :]) for i in range(len(v))])

def quantile_test(τ):
    ϕ = [0.1, 0.2, 0.7]
    v = [10, 20, 30]

    #d = DiscreteNonParametric(v, ϕ)
    d = rv_discrete(values=(v, ϕ))
    return quantile(τ, v, ϕ), d.ppf(τ)
    

quantile_js.py#

Hide code cell source
"""
Job search with Markov wage draws and quantile preferences.

"""
from quantecon import tauchen, MarkovChain
import numpy as np
from quantile_function import *

"Creates an instance of the job search model."
def create_markov_js_model(
        n=100,       # wage grid size
        ρ=0.9,       # wage persistence
        ν=0.2,       # wage volatility
        β=0.98,      # discount factor
        c=1.0,       # unemployment compensation
        τ=0.5        # quantile parameter
    ):
    mc = tauchen(n, ρ, ν)
    w_vals, P = np.exp(mc.state_values), mc.P
    return (n, w_vals, P, β, c, τ)

"""
The policy operator 

    (T_σ v)(w) = σ(w) (w / (1-β)) + (1 - σ(w))(c + β (R_τ v)(w))

"""
def T_σ(v, σ, model):
    n, w_vals, P, β, c, τ = model
    h = [x + c for x in β * R(τ, v, P)]
    e = w_vals / (1 - β)
    return σ * e + (1 - σ) * h

" Get a v-greedy policy."
def get_greedy(v, model):
    n, w_vals, P, β, c, τ = model
    σ = w_vals / (1 - β) >= c + β * R(τ, v, P)
    return σ


"Optimistic policy iteration routine."
def optimistic_policy_iteration(model, tolerance=1e-5, m=100):
    n, w_vals, P, β, c, τ = model
    v = np.ones(n)
    error = tolerance + 1
    while error > tolerance:
        last_v = v
        σ = get_greedy(v, model)
        for i in range(m):
            v = T_σ(v, σ, model)
        
        error = max(np.abs(v - last_v))
        print(f"OPI current error = {error}")
    
    return v, get_greedy(v, model)


# == Plots == #

import matplotlib.pyplot as plt
import matplotlib.pyplot as plt

plt.rcParams.update({"text.usetex": True, "font.size": 14})


def plot_main(tau_vals=(0.1, 0.25, 0.5, 0.6, 0.7, 0.8), 
                     savefig=False, 
                     figname="figures/quantile_js.pdf"):

    w_star_vals = np.zeros(len(tau_vals))

    for (τ_idx, τ) in enumerate(tau_vals):
        model = create_markov_js_model(τ=τ)
        n, w_vals, P, β, c, τ = model
        v_star, σ_star = optimistic_policy_iteration(model)
        for i in range(n):
            if σ_star[i] > 0:
                w_star_vals[τ_idx] = w_vals[i]
                break

    model = create_markov_js_model()
    n, w_vals, P, β, c, τ = model
    mc = MarkovChain(P)
    s = mc.stationary_distributions[0]

    fig, ax = plt.subplots()
    ax.plot(tau_vals, w_star_vals, "k--", lw=2, alpha=0.7, label="reservation wage")
    ax.barh(w_vals, 32 * s, alpha=0.05, align="center")
    ax.legend(frameon=False, loc="upper center")
    ax.set_xlabel("quantile")
    ax.set_ylabel("wages")

    if savefig:
        fig.savefig(figname)
plot_main()
OPI current error = 197.04958104222325
OPI current error = 32.41999066047552
OPI current error = 35.62127408582426
OPI current error = 34.1446640510786
OPI current error = 31.357190366860497
OPI current error = 27.984260985215176
OPI current error = 23.254898512652094
OPI current error = 16.591569430034006
OPI current error = 7.09109988950496
OPI current error = 3.102226038720346e-06
OPI current error = 197.04958104222325
OPI current error = 32.41999066047552
OPI current error = 29.678936943622382
OPI current error = 29.093921296620483
OPI current error = 27.974814794615806
OPI current error = 26.72531111677077
OPI current error = 25.39554124988487
OPI current error = 23.255076254919008
OPI current error = 20.92822560847147
OPI current error = 18.39891195068428
OPI current error = 14.680917085664738
OPI current error = 10.525484762612273
OPI current error = 5.881149036250456
OPI current error = 9.59630597208161e-10
OPI current error = 197.04958104222325
OPI current error = 36.59694294236868
OPI current error = 0.676518038229375
OPI current error = 0.08971952178475817
OPI current error = 0.011898563134202789
OPI current error = 0.001577982158643465
OPI current error = 0.00020927129308034864
OPI current error = 2.7753465943192168e-05
OPI current error = 3.6806523482368902e-06
OPI current error = 197.04958104222325
OPI current error = 51.40389833249008
OPI current error = 0.0
OPI current error = 197.04958104222325
OPI current error = 82.51356008425164
OPI current error = 0.0
OPI current error = 197.04958104222325
OPI current error = 133.14095588610525
OPI current error = 0.0
Error in callback <function _draw_all_if_interactive at 0x7f190daf5c60> (for post_execute):
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/texmanager.py:255, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    254 try:
--> 255     report = subprocess.check_output(
    256         command, cwd=cwd if cwd is not None else cls.texcache,
    257         stderr=subprocess.STDOUT)
    258 except FileNotFoundError as exc:

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/subprocess.py:421, in check_output(timeout, *popenargs, **kwargs)
    419     kwargs['input'] = empty
--> 421 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    422            **kwargs).stdout

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/subprocess.py:503, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    501     kwargs['stderr'] = PIPE
--> 503 with Popen(*popenargs, **kwargs) as process:
    504     try:

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/subprocess.py:971, 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)
    968             self.stderr = io.TextIOWrapper(self.stderr,
    969                     encoding=encoding, errors=errors)
--> 971     self._execute_child(args, executable, preexec_fn, close_fds,
    972                         pass_fds, cwd, env,
    973                         startupinfo, creationflags, shell,
    974                         p2cread, p2cwrite,
    975                         c2pread, c2pwrite,
    976                         errread, errwrite,
    977                         restore_signals,
    978                         gid, gids, uid, umask,
    979                         start_new_session)
    980 except:
    981     # Cleanup if the child failed starting.

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/subprocess.py:1863, 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)
   1862         err_msg = os.strerror(errno_num)
-> 1863     raise child_exception_type(errno_num, err_msg, err_filename)
   1864 raise child_exception_type(err_msg)

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/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/pyplot.py:120, in _draw_all_if_interactive()
    118 def _draw_all_if_interactive():
    119     if matplotlib.is_interactive():
--> 120         draw_all()

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/_pylab_helpers.py:132, in Gcf.draw_all(cls, force)
    130 for manager in cls.get_all_fig_managers():
    131     if force or manager.canvas.figure.stale:
--> 132         manager.canvas.draw_idle()

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/backend_bases.py:2082, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
   2080 if not self._is_idle_drawing:
   2081     with self._idle_draw_cntx():
-> 2082         self.draw(*args, **kwargs)

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/backends/backend_agg.py:400, in FigureCanvasAgg.draw(self)
    396 # Acquire a lock on the shared font cache.
    397 with RendererAgg.lock, \
    398      (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    399       else nullcontext()):
--> 400     self.figure.draw(self.renderer)
    401     # A GUI class may be need to update a window using this draw, so
    402     # don't forget to call the superclass.
    403     super().draw()

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

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/artist.py:72, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     69     if artist.get_agg_filter() is not None:
     70         renderer.start_filter()
---> 72     return draw(artist, renderer)
     73 finally:
     74     if artist.get_agg_filter() is not None:

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/figure.py:3140, in Figure.draw(self, renderer)
   3137         # ValueError can occur when resizing a window.
   3139 self.patch.draw(renderer)
-> 3140 mimage._draw_list_compositing_images(
   3141     renderer, self, artists, self.suppressComposite)
   3143 for sfig in self.subfigs:
   3144     sfig.draw(renderer)

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/image.py:131, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    129 if not_composite or not has_images:
    130     for a in artists:
--> 131         a.draw(renderer)
    132 else:
    133     # Composite any adjacent images together
    134     image_group = []

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/artist.py:72, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     69     if artist.get_agg_filter() is not None:
     70         renderer.start_filter()
---> 72     return draw(artist, renderer)
     73 finally:
     74     if artist.get_agg_filter() is not None:

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/axes/_base.py:3064, in _AxesBase.draw(self, renderer)
   3061 if artists_rasterized:
   3062     _draw_rasterized(self.figure, artists_rasterized, renderer)
-> 3064 mimage._draw_list_compositing_images(
   3065     renderer, self, artists, self.figure.suppressComposite)
   3067 renderer.close_group('axes')
   3068 self.stale = False

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/image.py:131, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    129 if not_composite or not has_images:
    130     for a in artists:
--> 131         a.draw(renderer)
    132 else:
    133     # Composite any adjacent images together
    134     image_group = []

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/artist.py:72, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     69     if artist.get_agg_filter() is not None:
     70         renderer.start_filter()
---> 72     return draw(artist, renderer)
     73 finally:
     74     if artist.get_agg_filter() is not None:

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/axis.py:1377, in Axis.draw(self, renderer, *args, **kwargs)
   1374 renderer.open_group(__name__, gid=self.get_gid())
   1376 ticks_to_draw = self._update_ticks()
-> 1377 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
   1379 for tick in ticks_to_draw:
   1380     tick.draw(renderer)

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/axis.py:1304, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
   1302 if renderer is None:
   1303     renderer = self.figure._get_renderer()
-> 1304 return ([tick.label1.get_window_extent(renderer)
   1305          for tick in ticks if tick.label1.get_visible()],
   1306         [tick.label2.get_window_extent(renderer)
   1307          for tick in ticks if tick.label2.get_visible()])

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/axis.py:1304, in <listcomp>(.0)
   1302 if renderer is None:
   1303     renderer = self.figure._get_renderer()
-> 1304 return ([tick.label1.get_window_extent(renderer)
   1305          for tick in ticks if tick.label1.get_visible()],
   1306         [tick.label2.get_window_extent(renderer)
   1307          for tick in ticks if tick.label2.get_visible()])

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/text.py:959, in Text.get_window_extent(self, renderer, dpi)
    954     raise RuntimeError(
    955         "Cannot get window extent of text w/o renderer. You likely "
    956         "want to call 'figure.draw_without_rendering()' first.")
    958 with cbook._setattr_cm(self.figure, dpi=dpi):
--> 959     bbox, info, descent = self._get_layout(self._renderer)
    960     x, y = self.get_unitless_position()
    961     x, y = self.get_transform().transform((x, y))

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/text.py:378, in Text._get_layout(self, renderer)
    375 ys = []
    377 # Full vertical extent of font, including ascenders and descenders:
--> 378 _, lp_h, lp_d = _get_text_metrics_with_cache(
    379     renderer, "lp", self._fontproperties,
    380     ismath="TeX" if self.get_usetex() else False, dpi=self.figure.dpi)
    381 min_dy = (lp_h - lp_d) * self._linespacing
    383 for i, line in enumerate(lines):

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/text.py:97, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
     94 """Call ``renderer.get_text_width_height_descent``, caching the results."""
     95 # Cached based on a copy of fontprop so that later in-place mutations of
     96 # the passed-in argument do not mess up the cache.
---> 97 return _get_text_metrics_with_cache_impl(
     98     weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/text.py:105, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
    101 @functools.lru_cache(4096)
    102 def _get_text_metrics_with_cache_impl(
    103         renderer_ref, text, fontprop, ismath, dpi):
    104     # dpi is unused, but participates in cache invalidation (via the renderer).
--> 105     return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/backends/backend_agg.py:226, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
    224 _api.check_in_list(["TeX", True, False], ismath=ismath)
    225 if ismath == "TeX":
--> 226     return super().get_text_width_height_descent(s, prop, ismath)
    228 if ismath:
    229     ox, oy, width, height, descent, font_image = \
    230         self.mathtext_parser.parse(s, self.dpi, prop)

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/backend_bases.py:645, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
    641 fontsize = prop.get_size_in_points()
    643 if ismath == 'TeX':
    644     # todo: handle properties
--> 645     return self.get_texmanager().get_text_width_height_descent(
    646         s, fontsize, renderer=self)
    648 dpi = self.points_to_pixels(72)
    649 if ismath:

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

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/texmanager.py:300, in TexManager.make_dvi(cls, tex, fontsize)
    298     with TemporaryDirectory(dir=cwd) as tmpdir:
    299         tmppath = Path(tmpdir)
--> 300         cls._run_checked_subprocess(
    301             ["latex", "-interaction=nonstopmode", "--halt-on-error",
    302              f"--output-directory={tmppath.name}",
    303              f"{texfile.name}"], tex, cwd=cwd)
    304         (tmppath / Path(dvifile).name).replace(dvifile)
    305 return dvifile

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/texmanager.py:259, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    255     report = subprocess.check_output(
    256         command, cwd=cwd if cwd is not None else cls.texcache,
    257         stderr=subprocess.STDOUT)
    258 except FileNotFoundError as exc:
--> 259     raise RuntimeError(
    260         'Failed to process string with tex because {} could not be '
    261         'found'.format(command[0])) from exc
    262 except subprocess.CalledProcessError as exc:
    263     raise RuntimeError(
    264         '{prog} was not able to process the following string:\n'
    265         '{tex!r}\n\n'
   (...)
    272             exc=exc.output.decode('utf-8', 'backslashreplace'))
    273         ) from None

RuntimeError: Failed to process string with tex because latex could not be found
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/texmanager.py:255, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    254 try:
--> 255     report = subprocess.check_output(
    256         command, cwd=cwd if cwd is not None else cls.texcache,
    257         stderr=subprocess.STDOUT)
    258 except FileNotFoundError as exc:

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/subprocess.py:421, in check_output(timeout, *popenargs, **kwargs)
    419     kwargs['input'] = empty
--> 421 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    422            **kwargs).stdout

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/subprocess.py:503, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    501     kwargs['stderr'] = PIPE
--> 503 with Popen(*popenargs, **kwargs) as process:
    504     try:

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/subprocess.py:971, 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)
    968             self.stderr = io.TextIOWrapper(self.stderr,
    969                     encoding=encoding, errors=errors)
--> 971     self._execute_child(args, executable, preexec_fn, close_fds,
    972                         pass_fds, cwd, env,
    973                         startupinfo, creationflags, shell,
    974                         p2cread, p2cwrite,
    975                         c2pread, c2pwrite,
    976                         errread, errwrite,
    977                         restore_signals,
    978                         gid, gids, uid, umask,
    979                         start_new_session)
    980 except:
    981     # Cleanup if the child failed starting.

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/subprocess.py:1863, 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)
   1862         err_msg = os.strerror(errno_num)
-> 1863     raise child_exception_type(errno_num, err_msg, err_filename)
   1864 raise child_exception_type(err_msg)

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/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/IPython/core/formatters.py:340, in BaseFormatter.__call__(self, obj)
    338     pass
    339 else:
--> 340     return printer(obj)
    341 # Finally look for special method names
    342 method = get_real_method(obj, self.print_method)

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/IPython/core/pylabtools.py:152, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
    149     from matplotlib.backend_bases import FigureCanvasBase
    150     FigureCanvasBase(fig)
--> 152 fig.canvas.print_figure(bytes_io, **kw)
    153 data = bytes_io.getvalue()
    154 if fmt == 'svg':

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/backend_bases.py:2342, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2336     renderer = _get_renderer(
   2337         self.figure,
   2338         functools.partial(
   2339             print_method, orientation=orientation)
   2340     )
   2341     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2342         self.figure.draw(renderer)
   2344 if bbox_inches:
   2345     if bbox_inches == "tight":

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

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/artist.py:72, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     69     if artist.get_agg_filter() is not None:
     70         renderer.start_filter()
---> 72     return draw(artist, renderer)
     73 finally:
     74     if artist.get_agg_filter() is not None:

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/figure.py:3140, in Figure.draw(self, renderer)
   3137         # ValueError can occur when resizing a window.
   3139 self.patch.draw(renderer)
-> 3140 mimage._draw_list_compositing_images(
   3141     renderer, self, artists, self.suppressComposite)
   3143 for sfig in self.subfigs:
   3144     sfig.draw(renderer)

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/image.py:131, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    129 if not_composite or not has_images:
    130     for a in artists:
--> 131         a.draw(renderer)
    132 else:
    133     # Composite any adjacent images together
    134     image_group = []

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/artist.py:72, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     69     if artist.get_agg_filter() is not None:
     70         renderer.start_filter()
---> 72     return draw(artist, renderer)
     73 finally:
     74     if artist.get_agg_filter() is not None:

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/axes/_base.py:3064, in _AxesBase.draw(self, renderer)
   3061 if artists_rasterized:
   3062     _draw_rasterized(self.figure, artists_rasterized, renderer)
-> 3064 mimage._draw_list_compositing_images(
   3065     renderer, self, artists, self.figure.suppressComposite)
   3067 renderer.close_group('axes')
   3068 self.stale = False

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/image.py:131, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    129 if not_composite or not has_images:
    130     for a in artists:
--> 131         a.draw(renderer)
    132 else:
    133     # Composite any adjacent images together
    134     image_group = []

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/artist.py:72, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     69     if artist.get_agg_filter() is not None:
     70         renderer.start_filter()
---> 72     return draw(artist, renderer)
     73 finally:
     74     if artist.get_agg_filter() is not None:

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/axis.py:1377, in Axis.draw(self, renderer, *args, **kwargs)
   1374 renderer.open_group(__name__, gid=self.get_gid())
   1376 ticks_to_draw = self._update_ticks()
-> 1377 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
   1379 for tick in ticks_to_draw:
   1380     tick.draw(renderer)

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/axis.py:1304, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
   1302 if renderer is None:
   1303     renderer = self.figure._get_renderer()
-> 1304 return ([tick.label1.get_window_extent(renderer)
   1305          for tick in ticks if tick.label1.get_visible()],
   1306         [tick.label2.get_window_extent(renderer)
   1307          for tick in ticks if tick.label2.get_visible()])

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/axis.py:1304, in <listcomp>(.0)
   1302 if renderer is None:
   1303     renderer = self.figure._get_renderer()
-> 1304 return ([tick.label1.get_window_extent(renderer)
   1305          for tick in ticks if tick.label1.get_visible()],
   1306         [tick.label2.get_window_extent(renderer)
   1307          for tick in ticks if tick.label2.get_visible()])

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/text.py:959, in Text.get_window_extent(self, renderer, dpi)
    954     raise RuntimeError(
    955         "Cannot get window extent of text w/o renderer. You likely "
    956         "want to call 'figure.draw_without_rendering()' first.")
    958 with cbook._setattr_cm(self.figure, dpi=dpi):
--> 959     bbox, info, descent = self._get_layout(self._renderer)
    960     x, y = self.get_unitless_position()
    961     x, y = self.get_transform().transform((x, y))

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/text.py:378, in Text._get_layout(self, renderer)
    375 ys = []
    377 # Full vertical extent of font, including ascenders and descenders:
--> 378 _, lp_h, lp_d = _get_text_metrics_with_cache(
    379     renderer, "lp", self._fontproperties,
    380     ismath="TeX" if self.get_usetex() else False, dpi=self.figure.dpi)
    381 min_dy = (lp_h - lp_d) * self._linespacing
    383 for i, line in enumerate(lines):

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/text.py:97, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
     94 """Call ``renderer.get_text_width_height_descent``, caching the results."""
     95 # Cached based on a copy of fontprop so that later in-place mutations of
     96 # the passed-in argument do not mess up the cache.
---> 97 return _get_text_metrics_with_cache_impl(
     98     weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/text.py:105, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
    101 @functools.lru_cache(4096)
    102 def _get_text_metrics_with_cache_impl(
    103         renderer_ref, text, fontprop, ismath, dpi):
    104     # dpi is unused, but participates in cache invalidation (via the renderer).
--> 105     return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/backends/backend_agg.py:226, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
    224 _api.check_in_list(["TeX", True, False], ismath=ismath)
    225 if ismath == "TeX":
--> 226     return super().get_text_width_height_descent(s, prop, ismath)
    228 if ismath:
    229     ox, oy, width, height, descent, font_image = \
    230         self.mathtext_parser.parse(s, self.dpi, prop)

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/backend_bases.py:645, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
    641 fontsize = prop.get_size_in_points()
    643 if ismath == 'TeX':
    644     # todo: handle properties
--> 645     return self.get_texmanager().get_text_width_height_descent(
    646         s, fontsize, renderer=self)
    648 dpi = self.points_to_pixels(72)
    649 if ismath:

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

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/texmanager.py:300, in TexManager.make_dvi(cls, tex, fontsize)
    298     with TemporaryDirectory(dir=cwd) as tmpdir:
    299         tmppath = Path(tmpdir)
--> 300         cls._run_checked_subprocess(
    301             ["latex", "-interaction=nonstopmode", "--halt-on-error",
    302              f"--output-directory={tmppath.name}",
    303              f"{texfile.name}"], tex, cwd=cwd)
    304         (tmppath / Path(dvifile).name).replace(dvifile)
    305 return dvifile

File /usr/share/miniconda3/envs/dse2023/lib/python3.10/site-packages/matplotlib/texmanager.py:259, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    255     report = subprocess.check_output(
    256         command, cwd=cwd if cwd is not None else cls.texcache,
    257         stderr=subprocess.STDOUT)
    258 except FileNotFoundError as exc:
--> 259     raise RuntimeError(
    260         'Failed to process string with tex because {} could not be '
    261         'found'.format(command[0])) from exc
    262 except subprocess.CalledProcessError as exc:
    263     raise RuntimeError(
    264         '{prog} was not able to process the following string:\n'
    265         '{tex!r}\n\n'
   (...)
    272             exc=exc.output.decode('utf-8', 'backslashreplace'))
    273         ) from None

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