Source code for autofit.non_linear.plot.samples_plotters
import logging
import numpy as np
from autonerves import conf
from autofit.non_linear.plot.plot_util import (
skip_in_test_mode,
output_figure,
accepted_kwarg_names,
checked_kwargs,
)
logger = logging.getLogger(__name__)
def _corner_range_from(data):
"""Per-column ``(min, max)`` plot ranges for a corner figure.
``corner`` raises "no dynamic range" if any column has zero spread (every
sample equal). That is a legitimate input — a reduced-iteration run (e.g.
``PYAUTO_TEST_MODE=1``) or a parameter that has converged flat — so we hand
it an explicit per-column range rather than let it crash. Columns with real
spread keep their ``(min, max)``; degenerate columns are widened to a small
non-zero window centred on the value so the plot still renders.
"""
mins = np.asarray(data).min(axis=0)
maxs = np.asarray(data).max(axis=0)
plot_range = []
for lower, upper in zip(mins, maxs):
if lower == upper:
pad = max(abs(lower) * 1e-4, 1e-8)
lower, upper = lower - pad, upper + pad
plot_range.append((lower, upper))
return plot_range
def _effective_sample_size(weight_list, sample_count):
"""Kish effective sample size ``sum(w)**2 / sum(w**2)`` of a weight list.
Row count alone does not say whether a sample can be plotted: a nested
sampler stopped early returns many rows of which one carries essentially all
the weight, so every weighted statistic ``corner`` computes (quantile
``range`` most visibly) is a statistic of that single point. ``None`` or an
empty weight list means unweighted, for which every row counts.
"""
if weight_list is None:
return float(sample_count)
weights = np.asarray(weight_list, dtype=float)
if weights.size == 0:
return float(sample_count)
sum_of_squares = np.sum(weights**2)
if sum_of_squares == 0.0:
return 0.0
return float(np.sum(weights) ** 2 / sum_of_squares)
[docs]
@skip_in_test_mode
def corner_cornerpy(samples, path=None, filename="corner", format="show", **kwargs):
data = np.asarray(samples.parameter_lists)
sample_count = data.shape[0] if data.ndim >= 1 else 0
parameter_count = data.shape[1] if data.ndim >= 2 else 0
effective_sample_size = _effective_sample_size(
getattr(samples, "weight_list", None), sample_count
)
if (
data.ndim < 2
or sample_count <= parameter_count
or effective_sample_size <= parameter_count
):
logger.info(
"corner_cornerpy: skipping corner plot, only %s sample(s) "
"(effective sample size %.2f) for %s parameter(s) "
"(e.g. PYAUTO_TEST_MODE bypass or an early-iteration update).",
sample_count,
effective_sample_size,
parameter_count,
)
return
import matplotlib.pylab as pylab
config_dict = conf.instance["visualize"]["plots_settings"]["corner_cornerpy"]
params = {"font.size": int(config_dict["fontsize"])}
pylab.rcParams.update(params)
import corner
from corner.core import hist2d as corner_hist2d
settings = dict(
weights=samples.weight_list,
labels=samples.model.parameter_labels_with_superscripts_latex,
range=_corner_range_from(data),
)
# ``hist2d``'s named parameters are a documented corner pass-through, so they
# stay valid; ``x`` / ``y`` are its positional sample arrays, not options.
accepts = accepted_kwarg_names(corner.corner, corner_hist2d) - {"x", "y"}
for key, value in checked_kwargs(
kwargs, accepts=accepts, reserved=("data",), target="corner.corner"
).items():
# ``None`` against a value PyAutoFit computes means "use the default" —
# notably for ``range``, where blanking it would hand ``corner`` back the
# degenerate columns ``_corner_range_from`` exists to widen.
if value is None and key in settings:
continue
settings[key] = value
corner.corner(data=data, **settings)
output_figure(path=path, filename=filename, format=format)