Skip to content

fix: autofit.plot silently discards **kwargs and corner weights #1523

Description

@Jammy2211

Overview

All five public autofit.plot functions declare **kwargs and never reference it — a caller's customization is accepted without error and has no effect. The failure is silent: no TypeError, no warning, just a plot that ignores what you asked for. autofit_workspace/scripts/plot/*.py teaches the broken idiom at four call sites (76 kwargs), and states in prose that the kwargs are forwarded; PyAutoFit/docs/cookbooks/search.md carries the same claim. Verifying the report surfaced a second silent defect in the same call: corner_cornerpy passes weight_list= to corner.corner, which has no such parameter (it is weights) — so sample weights are currently ignored on every weighted corner plot.

Fix direction chosen: forward the kwargs, with a strict signature filter. Unrecognised names raise TypeError rather than vanishing. Both defects are fixed in the same wave.

Plan

  • Add a shared kwargs-forwarding helper to plot_util.py that validates caller kwargs against the target function's named parameters and raises TypeError on anything unrecognised, with a did-you-mean hint.
  • Forward validated kwargs from all five plot functions to their underlying library (corner.corner, anesthetic, matplotlib), with library-computed values acting as defaults the caller can override.
  • Fix the weight_list=weights= bug so weighted posteriors plot correctly.
  • Preserve the _corner_range_from degenerate-column guard: it applies whenever the caller omits range or passes None.
  • Stop @log_plot_exception swallowing the new validation TypeError on corner_anesthetic.
  • Rewrite the four workspace scripts' kwarg lists to genuine corner.py kwargs and correct the prose that claims forwarding — three of the four were copy-pasted from a different sampler's plotting API.
  • Cover both defects with tests, and confirm test-mode/degenerate input still renders.
Detailed implementation plan

Work Classification

Both (library + workspace). The library PR merges first; the workspace PR follows behind the library-first merge gate.

Affected Repositories

  • PyAutoLabs/PyAutoFit (primary)
  • PyAutoLabs/autofit_workspace

Branch Survey

Repository Current Branch Dirty?
PyAutoFit main clean
autofit_workspace main clean

worktree_check_conflict autofit-plot-functions-kwargs PyAutoFit autofit_workspace → exit 0 (no conflict). autofit_workspace carries a stale unregistered branch feature/plot-api-update (HEAD 389e968, "Regenerate notebooks from updated scripts") — a warning, not a block.

Suggested branch: claude/autofit-plot-functions-kwargs-vvwj5x (both repos)

Worktree root: ~/Code/PyAutoLabs-wt/autofit-plot-functions-kwargs/ (created later by /start_library)

Grounding

Verified against PyAutoFit 055bb3d99 and autofit_workspace 94671f62, with corner 2.3.0 installed and exercised.

The **kwargs are dead. grep -n kwargs over the three plot modules matches only def lines.

weights are dropped. corner.corner's signature has weights, not weight_list. Unrecognised names fall into corner_impl(**hist2d_kwargs)hist2d(**kwargs), whose body reads only extent and discards the rest. Confirmed empirically: corner.corner(data=..., weight_list=w, ...) returns a figure with no error and unweighted contours.

Forwarding alone would not fix the tutorials. Because of that same hist2d sink, wrong-library kwargs would stay silently ignored one layer deeper. Measured against corner 2.3.0's real signature:

script kwargs take effect still dropped
emcee_plotter.py 30 30 0
dynesty_plotter.py 19 14 5 — dims, span, quantiles_2d, hist2d_kwargs, truth_kwargs (dynesty's API)
zeus_plotter.py 16 6 10 — span, truth, alpha, linewidth, fill, fontsize, title_fontsize, cut, size, weight_list (zeus's API)
nautilus_plotter.py 11 8 3 — panelsize, yticksize, xticksize

This is why the fix is forward + strict filter, not plain forwarding: the filter converts those 18 into loud errors, and the workspace rewrite removes them.

Collision hazards.

  • zeus_plotter.py passes weight_list=None, which duplicates the library's own weight_list= keyword → TypeError: got multiple values for keyword argument. A hard crash, not silence.
  • range collides twice: emcee_plotter.py passes range=None, nautilus_plotter.py passes range=np.ones(n) * 0.999. Naive caller-wins defeats _corner_range_from's degenerate-column guard.
  • corner_anesthetic is wrapped in @log_plot_exception, which catches TypeError — a validation error there would surface as a misleading "posterior estimate not yet sufficient" info log.

Implementation Steps — PyAutoFit (PR 1, merges first)

  1. autofit/non_linear/plot/plot_util.py — add the forwarding helper:

    def forward_kwargs(kwargs, *, accepts, reserved=(), target=""):

    accepts is the set of parameter names the target genuinely honours; a target's own **kwargs is deliberately not treated as "accepts anything" (corner's hist2d_kwargs sink is exactly the trap being closed). Unrecognised names raise a PlotKwargsError(TypeError) naming them, with difflib.get_close_matches supplying a hint (weight_list → "did you mean weights?"). reserved names arguments the library owns and the caller may never override.

    For corner, accepts = named parameters of corner.corner ∪ named parameters of corner.core.hist2d (minus x, y) — hist2d kwargs are a documented, legitimate pass-through, so they stay valid.

  2. samples_plotters.py::corner_cornerpy

    • weight_list=weights=.
    • Build defaults = {"weights": ..., "labels": ..., "range": _corner_range_from(data)}, validate the caller's kwargs, then defaults.update(validated). data is reserved.
    • range: the guard is the default, so it applies when the caller omits range or passes None; a non-None caller value wins.
    • Keep the existing data.ndim < 2 or data.shape[0] <= data.shape[1] early return.
  3. nest_plotters.py::corner_anesthetic — route validated kwargs to their actual targets (make_2d_axes vs NestedSamples.plot_2d); anything matching neither raises. Narrow @log_plot_exception to re-raise PlotKwargsError so validation failures are not reported as an unconverged posterior.

  4. mle_plotters.pysubplot_parameters, log_likelihood_vs_iteration, figure_of_merit_vs_iteration: forward validated kwargs to plt.plot / plt.semilogy (Line2D properties), reserving c.

  5. Internal callersabstract_mcmc.py:56, abstract_nest.py:77, abstract_mle.py:38-64 pass only samples/path/format/the explicit flags, so no call-site changes; confirm none regress.

  6. docs/cookbooks/search.md:165 — the 30-kwarg emcee list stays valid (all 30 are genuine corner kwargs); update the surrounding prose to state that kwargs are forwarded and validated.

  7. Teststest_autofit/non_linear/plot/test_samples_plotters.py (+ new modules for nest/mle):

    • a recognised kwarg reaches the target (monkeypatch corner.corner, assert receipt);
    • an unrecognised kwarg raises TypeError naming it;
    • weights is passed through as weights= and equals samples.weight_list;
    • caller range=None still yields the _corner_range_from guard; a non-None range wins;
    • degenerate-column input (every sample equal) still renders without corner's "no dynamic range" crash;
    • corner_anesthetic validation error is not swallowed by @log_plot_exception.

Implementation Steps — autofit_workspace (PR 2, behind the library-first gate)

  1. Rewrite the four scripts/plot/*.py kwarg lists to genuine corner.py kwargs only — drop dynesty's 5, zeus's 10, nautilus's 3. zeus_plotter.py's weight_list=None must go.
  2. Correct the prose in each script so the claim matches behaviour (kwargs are forwarded to corner.py and validated).
  3. Regenerate the notebooks from the scripts per workspace convention.

Verify

  • A call passing a non-default kwarg (e.g. bins=5) visibly changes the output figure.
  • A bogus kwarg raises TypeError naming it, rather than being ignored.
  • Weighted samples produce a visibly different corner figure than the same samples unweighted.
  • Degenerate-column input (e.g. a PYAUTO_TEST_MODE=1 run) still renders — the _corner_range_from guard survives.
  • No workspace script still passes an argument that does nothing.

Key Files

  • autofit/non_linear/plot/plot_util.py — new forward_kwargs helper + PlotKwargsError; log_plot_exception narrowed
  • autofit/non_linear/plot/samples_plotters.pycorner_cornerpy: forwarding + the weights fix
  • autofit/non_linear/plot/nest_plotters.pycorner_anesthetic: split forwarding
  • autofit/non_linear/plot/mle_plotters.py — the three matplotlib trace plots
  • docs/cookbooks/search.md — prose claim about kwargs
  • test_autofit/non_linear/plot/test_samples_plotters.py — extended coverage
  • autofit_workspace/scripts/plot/{emcee,dynesty,zeus,nautilus}_plotter.py — kwarg lists + prose

Sizing note

pyauto-brain bug and the sizing faculty both return too-large (score 13) and recommend phasing. Recorded and overridden: the score is inflated by prompt word-count plus the multi-repo flag. The actual change is ~15 lines of library code, four workspace scripts, and one docs snippet — the prompt's own Difficulty: small header is the better estimate. Not phased; one library PR plus its paired workspace PR.

Original Prompt

Click to expand starting prompt

autofit.plot functions accept **kwargs and silently discard them

Type: bug
Target: autofit
Repos:

  • PyAutoFit
  • autofit_workspace
    Difficulty: small
    Autonomy: supervised
    Priority: normal
    Status: formalised
    Filed: 2026-08-07 (backfilled from git)

Filed 2026-08-07, found while fixing docs/api/plot.rst
(complete/2026/08/pyautofit_plot_rst_dead_plotters.md). Deliberately left out of
that PR: it is a library/workspace defect, not a docs one, and the docs change
was docs-only by design.

The defect

All five public autofit.plot functions take **kwargs in their signature and
never reference it in the body. Verified mechanically — kwargs appears in
autofit/non_linear/plot/{samples_plotters,nest_plotters,mle_plotters}.py only
on the def lines:

  • corner_cornerpy(samples, path=None, filename="corner", format="show", **kwargs)
  • corner_anesthetic(samples, path=None, filename="corner_anesthetic", format="show", **kwargs)
  • subplot_parameters(...), log_likelihood_vs_iteration(...),
    figure_of_merit_vs_iteration(...) — same shape

corner_cornerpy calls the underlying library with a fixed argument set:

corner.corner(
    data=data,
    weight_list=samples.weight_list,
    labels=samples.model.parameter_labels_with_superscripts_latex,
    range=_corner_range_from(data),
)

So a caller's customization is accepted without error and has no effect. The
failure mode is the bad one: silent. No TypeError, no warning, just a plot
that ignores what you asked for.

Why it matters — the workspace teaches the broken idiom

autofit_workspace/scripts/plot/*.py passes long kwarg lists as though they
were forwarded, and says so in prose: "In all the examples below, we use the
kwargs of this function to pass in any of the input parameters that are
described in the API docs."
That claim is false today.

Silently-discarded kwargs at those call sites (counted 2026-08-07):

script discarded kwargs
scripts/plot/emcee_plotter.py 30
scripts/plot/dynesty_plotter.py 19
scripts/plot/zeus_plotter.py 16
scripts/plot/nautilus_plotter.py 11
total 76

A user following the plot tutorials sets bins, smooth, show_titles,
truths, … and sees none of them applied. The tutorials are the documentation
for this API, so this is the primary way the behaviour is encountered.

The decision to make (why supervised, not auto)

Two coherent fixes; picking one is a judgement about the intended surface:

  1. Forward them — pass **kwargs through to corner.corner /
    anesthetic / matplotlib. Matches what the workspace already claims and
    makes the existing tutorials correct as written. Watch the collisions:
    corner_cornerpy already sets data, weight_list, labels and range
    explicitly, and range in particular is computed by _corner_range_from
    to dodge corner's "no dynamic range" crash on degenerate columns — a
    user-supplied range must not silently reintroduce that. Decide precedence
    (caller wins / library wins) and state it.
  2. Drop **kwargs from the signatures and correct the workspace scripts +
    prose. Honest, and callers get a loud TypeError instead of silence — but
    it removes customization the tutorials imply exists, so it is the bigger
    user-facing change.

Either way the workspace scripts and their prose need updating in the same
wave, so this is a paired PyAutoFit + autofit_workspace task.

Verify

  • A call passing a non-default kwarg (e.g. bins=5) visibly changes the
    output figure (fix 1), or raises TypeError (fix 2).
  • The four scripts/plot/*.py tutorials and their surrounding prose agree with
    whichever behaviour was chosen — no script still passes an argument that does
    nothing.
  • Degenerate-column input (every sample equal, e.g. a PYAUTO_TEST_MODE=1 run)
    still does not crash corner, i.e. the _corner_range_from guard survives.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions